Better client/login/authState handling
This commit is contained in:
parent
ec506a7f9e
commit
ac299ec7ba
@ -1,18 +1,19 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
|
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/key_token_auth.dart';
|
|
||||||
import 'package:simplecloudnotifier/models/keytoken.dart';
|
import 'package:simplecloudnotifier/models/keytoken.dart';
|
||||||
import 'package:simplecloudnotifier/models/subscription.dart';
|
import 'package:simplecloudnotifier/models/subscription.dart';
|
||||||
import 'package:simplecloudnotifier/models/user.dart';
|
import 'package:simplecloudnotifier/models/user.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||||
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/state/token_source.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
enum ChannelSelector {
|
enum ChannelSelector {
|
||||||
@ -84,7 +85,7 @@ class APIClient {
|
|||||||
|
|
||||||
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
|
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
|
||||||
Toaster.error("Error", 'Request "${name}" failed');
|
Toaster.error("Error", 'Request "${name}" failed');
|
||||||
throw Exception(apierr.message);
|
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);
|
||||||
}
|
}
|
||||||
@ -117,21 +118,21 @@ class APIClient {
|
|||||||
|
|
||||||
// ==========================================================================================================================================================
|
// ==========================================================================================================================================================
|
||||||
|
|
||||||
static Future<User> getUser(KeyTokenAuth auth, String uid) async {
|
static Future<User> getUser(TokenSource auth, String uid) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getUser',
|
name: 'getUser',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'users/$uid',
|
relURL: 'users/$uid',
|
||||||
fn: User.fromJson,
|
fn: User.fromJson,
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Client> addClient(KeyTokenAuth? auth, String fcmToken, String agentModel, String agentVersion, String? name, String clientType) async {
|
static Future<Client> addClient(TokenSource auth, String fcmToken, String agentModel, String agentVersion, String? name, String clientType) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'addClient',
|
name: 'addClient',
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
relURL: 'users/${auth!.userId}/clients',
|
relURL: 'users/${auth.getUserID()}/clients',
|
||||||
jsonBody: {
|
jsonBody: {
|
||||||
'fcm_token': fcmToken,
|
'fcm_token': fcmToken,
|
||||||
'agent_model': agentModel,
|
'agent_model': agentModel,
|
||||||
@ -140,15 +141,15 @@ class APIClient {
|
|||||||
'name': name,
|
'name': name,
|
||||||
},
|
},
|
||||||
fn: Client.fromJson,
|
fn: Client.fromJson,
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Client> updateClient(KeyTokenAuth? auth, String clientID, String fcmToken, String agentModel, String? name, String agentVersion) async {
|
static Future<Client> updateClient(TokenSource auth, String clientID, String fcmToken, String agentModel, String? name, String agentVersion) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'updateClient',
|
name: 'updateClient',
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
relURL: 'users/${auth!.userId}/clients/$clientID',
|
relURL: 'users/${auth.getUserID()}/clients/$clientID',
|
||||||
jsonBody: {
|
jsonBody: {
|
||||||
'fcm_token': fcmToken,
|
'fcm_token': fcmToken,
|
||||||
'agent_model': agentModel,
|
'agent_model': agentModel,
|
||||||
@ -156,22 +157,32 @@ class APIClient {
|
|||||||
'name': name,
|
'name': name,
|
||||||
},
|
},
|
||||||
fn: Client.fromJson,
|
fn: Client.fromJson,
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<ChannelWithSubscription>> getChannelList(KeyTokenAuth auth, ChannelSelector sel) async {
|
static Future<Client> getClient(TokenSource auth, String cid) async {
|
||||||
|
return await _request(
|
||||||
|
name: 'getClient',
|
||||||
|
method: 'GET',
|
||||||
|
relURL: 'users/${auth.getUserID()}/clients/$cid',
|
||||||
|
fn: Client.fromJson,
|
||||||
|
authToken: auth.getToken(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future<List<ChannelWithSubscription>> getChannelList(TokenSource auth, ChannelSelector sel) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getChannelList',
|
name: 'getChannelList',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'users/${auth.userId}/channels',
|
relURL: 'users/${auth.getUserID()}/channels',
|
||||||
query: {'selector': sel.apiKey},
|
query: {'selector': sel.apiKey},
|
||||||
fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels'] as List<dynamic>),
|
fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels'] as List<dynamic>),
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<(String, List<Message>)> getMessageList(KeyTokenAuth auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
static Future<(String, List<Message>)> getMessageList(TokenSource auth, String pageToken, {int? pageSize, List<String>? channelIDs}) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getMessageList',
|
name: 'getMessageList',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@ -182,48 +193,48 @@ class APIClient {
|
|||||||
if (channelIDs != null) 'channel_id': channelIDs.join(","),
|
if (channelIDs != null) 'channel_id': channelIDs.join(","),
|
||||||
},
|
},
|
||||||
fn: (json) => Message.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
|
fn: (json) => Message.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<Message> getMessage(KeyTokenAuth auth, String msgid) async {
|
static Future<Message> getMessage(TokenSource auth, String msgid) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getMessage',
|
name: 'getMessage',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'messages/$msgid',
|
relURL: 'messages/$msgid',
|
||||||
query: {},
|
query: {},
|
||||||
fn: Message.fromJson,
|
fn: Message.fromJson,
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Subscription>> getSubscriptionList(KeyTokenAuth auth) async {
|
static Future<List<Subscription>> getSubscriptionList(TokenSource auth) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getSubscriptionList',
|
name: 'getSubscriptionList',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'users/${auth.userId}/subscriptions',
|
relURL: 'users/${auth.getUserID()}/subscriptions',
|
||||||
fn: (json) => Subscription.fromJsonArray(json['subscriptions'] as List<dynamic>),
|
fn: (json) => Subscription.fromJsonArray(json['subscriptions'] as List<dynamic>),
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<Client>> getClientList(KeyTokenAuth auth) async {
|
static Future<List<Client>> getClientList(TokenSource auth) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getClientList',
|
name: 'getClientList',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'users/${auth.userId}/clients',
|
relURL: 'users/${auth.getUserID()}/clients',
|
||||||
fn: (json) => Client.fromJsonArray(json['clients'] as List<dynamic>),
|
fn: (json) => Client.fromJsonArray(json['clients'] as List<dynamic>),
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<List<KeyToken>> getKeyTokenList(KeyTokenAuth auth) async {
|
static Future<List<KeyToken>> getKeyTokenList(TokenSource auth) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getKeyTokenList',
|
name: 'getKeyTokenList',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'users/${auth.userId}/keys',
|
relURL: 'users/${auth.getUserID()}/keys',
|
||||||
fn: (json) => KeyToken.fromJsonArray(json['keys'] as List<dynamic>),
|
fn: (json) => KeyToken.fromJsonArray(json['keys'] as List<dynamic>),
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,13 +256,13 @@ class APIClient {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<KeyToken> getKeyToken(KeyTokenAuth auth, String kid) async {
|
static Future<KeyToken> getKeyToken(TokenSource auth, String kid) async {
|
||||||
return await _request(
|
return await _request(
|
||||||
name: 'getKeyToken',
|
name: 'getKeyToken',
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
relURL: 'users/${auth.userId}/keys/$kid',
|
relURL: 'users/${auth.getUserID()}/keys/$kid',
|
||||||
fn: KeyToken.fromJson,
|
fn: KeyToken.fromJson,
|
||||||
authToken: auth.tokenAdmin,
|
authToken: auth.getToken(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
13
flutter/lib/api/api_exception.dart
Normal file
13
flutter/lib/api/api_exception.dart
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
class APIException implements Exception {
|
||||||
|
final int httpStatus;
|
||||||
|
final String error;
|
||||||
|
final String errHighlight;
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
APIException(this.httpStatus, this.error, this.errHighlight, this.message);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return '[$error] $message';
|
||||||
|
}
|
||||||
|
}
|
24
flutter/lib/components/hidable_fab/hidable_fab.dart
Normal file
24
flutter/lib/components/hidable_fab/hidable_fab.dart
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class HidableFAB extends StatelessWidget {
|
||||||
|
final VoidCallback? onPressed;
|
||||||
|
final IconData icon;
|
||||||
|
|
||||||
|
const HidableFAB({
|
||||||
|
super.key,
|
||||||
|
this.onPressed,
|
||||||
|
required this.icon,
|
||||||
|
});
|
||||||
|
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Visibility(
|
||||||
|
visible: MediaQuery.viewInsetsOf(context).bottom == 0.0, // hide when keyboard is shown
|
||||||
|
child: FloatingActionButton(
|
||||||
|
onPressed: onPressed,
|
||||||
|
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(17))),
|
||||||
|
elevation: 2.0,
|
||||||
|
child: Icon(icon),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -3,12 +3,13 @@ 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/client.dart';
|
||||||
import 'package:simplecloudnotifier/nav_layout.dart';
|
import 'package:simplecloudnotifier/nav_layout.dart';
|
||||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/user_account.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:firebase_core/firebase_core.dart';
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
import 'package:toastification/toastification.dart';
|
import 'package:toastification/toastification.dart';
|
||||||
import 'firebase_options.dart';
|
import 'firebase_options.dart';
|
||||||
@ -41,7 +42,20 @@ 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
UserAccount(); // ensure UserAccount is loaded
|
final appAuth = AppAuth(); // ensure UserAccount is loaded
|
||||||
|
|
||||||
|
if (appAuth.isAuth()) {
|
||||||
|
try {
|
||||||
|
await appAuth.loadUser();
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to load user (on startup): ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await appAuth.loadClient();
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to load user (on startup): ' + exc.toString(), trace: trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
|
||||||
|
|
||||||
@ -71,7 +85,7 @@ void main() async {
|
|||||||
runApp(
|
runApp(
|
||||||
MultiProvider(
|
MultiProvider(
|
||||||
providers: [
|
providers: [
|
||||||
ChangeNotifierProvider(create: (context) => UserAccount(), lazy: false),
|
ChangeNotifierProvider(create: (context) => AppAuth(), lazy: false),
|
||||||
ChangeNotifierProvider(create: (context) => AppTheme(), lazy: false),
|
ChangeNotifierProvider(create: (context) => AppTheme(), lazy: false),
|
||||||
],
|
],
|
||||||
child: const SCNApp(),
|
child: const SCNApp(),
|
||||||
@ -80,27 +94,38 @@ void main() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void setFirebaseToken(String fcmToken) async {
|
void setFirebaseToken(String fcmToken) async {
|
||||||
final acc = UserAccount();
|
final acc = AppAuth();
|
||||||
|
|
||||||
final oldToken = Globals().getPrefFCMToken();
|
final oldToken = Globals().getPrefFCMToken();
|
||||||
|
|
||||||
if (oldToken != null && oldToken == fcmToken && acc.client != null && acc.client!.fcmToken == fcmToken) {
|
await Globals().setPrefFCMToken(fcmToken);
|
||||||
|
|
||||||
|
ApplicationLog.info('New firebase token received', additional: 'Token: $fcmToken (old: $oldToken)');
|
||||||
|
|
||||||
|
if (!acc.isAuth()) return;
|
||||||
|
|
||||||
|
Client? client;
|
||||||
|
try {
|
||||||
|
client = await acc.loadClient(force: true);
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to get client: ' + exc.toString(), trace: trace);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (oldToken != null && oldToken == fcmToken && client != null && client!.fcmToken == fcmToken) {
|
||||||
ApplicationLog.info('Firebase token unchanged - do nothing', additional: 'Token: $fcmToken');
|
ApplicationLog.info('Firebase token unchanged - do nothing', additional: 'Token: $fcmToken');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationLog.info('New firebase token received', additional: 'Token: $fcmToken (old: $oldToken)');
|
if (client == null) {
|
||||||
|
// should not really happen - perhaps someone externally deleted the client?
|
||||||
await Globals().setPrefFCMToken(fcmToken);
|
final newClient = await APIClient.addClient(acc, fcmToken, Globals().deviceModel, Globals().version, Globals().hostname, Globals().clientType);
|
||||||
|
acc.setClientAndClientID(newClient);
|
||||||
if (acc.auth != null) {
|
await acc.save();
|
||||||
if (acc.client == null) {
|
|
||||||
final client = await APIClient.addClient(acc.auth, fcmToken, Globals().deviceModel, Globals().version, Globals().hostname, Globals().clientType);
|
|
||||||
acc.setClient(client);
|
|
||||||
} else {
|
} else {
|
||||||
final client = await APIClient.updateClient(acc.auth, acc.client!.clientID, fcmToken, Globals().deviceModel, Globals().hostname, Globals().version);
|
final newClient = await APIClient.updateClient(acc, client.clientID, fcmToken, Globals().deviceModel, Globals().hostname, Globals().version);
|
||||||
acc.setClient(client);
|
acc.setClientAndClientID(newClient);
|
||||||
}
|
await acc.save();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
class KeyTokenAuth {
|
|
||||||
final String userId;
|
|
||||||
final String tokenAdmin;
|
|
||||||
final String tokenSend;
|
|
||||||
|
|
||||||
KeyTokenAuth({
|
|
||||||
required this.userId,
|
|
||||||
required this.tokenAdmin,
|
|
||||||
required this.tokenSend,
|
|
||||||
});
|
|
||||||
}
|
|
@ -2,14 +2,15 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_lazy_indexed_stack/flutter_lazy_indexed_stack.dart';
|
import 'package:flutter_lazy_indexed_stack/flutter_lazy_indexed_stack.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/hidable_fab/hidable_fab.dart';
|
||||||
import 'package:simplecloudnotifier/components/layout/app_bar.dart';
|
import 'package:simplecloudnotifier/components/layout/app_bar.dart';
|
||||||
import 'package:simplecloudnotifier/pages/channel_list/channel_list.dart';
|
import 'package:simplecloudnotifier/pages/channel_list/channel_list.dart';
|
||||||
import 'package:simplecloudnotifier/pages/send/root.dart';
|
import 'package:simplecloudnotifier/pages/send/send.dart';
|
||||||
import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart';
|
import 'package:simplecloudnotifier/components/bottom_fab/fab_bottom_app_bar.dart';
|
||||||
import 'package:simplecloudnotifier/pages/account/account.dart';
|
import 'package:simplecloudnotifier/pages/account/account.dart';
|
||||||
import 'package:simplecloudnotifier/pages/message_list/message_list.dart';
|
import 'package:simplecloudnotifier/pages/message_list/message_list.dart';
|
||||||
import 'package:simplecloudnotifier/pages/settings/root.dart';
|
import 'package:simplecloudnotifier/pages/settings/root.dart';
|
||||||
import 'package:simplecloudnotifier/state/user_account.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
class SCNNavLayout extends StatefulWidget {
|
class SCNNavLayout extends StatefulWidget {
|
||||||
@ -24,15 +25,15 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
initState() {
|
initState() {
|
||||||
final userAcc = Provider.of<UserAccount>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
if (userAcc.auth == null) _selectedIndex = 2;
|
if (!userAcc.isAuth()) _selectedIndex = 2;
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onItemTapped(int index) {
|
void _onItemTapped(int index) {
|
||||||
final userAcc = Provider.of<UserAccount>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
if (userAcc.auth == null) {
|
if (!userAcc.isAuth()) {
|
||||||
Toaster.info("Not logged in", "Please login or create a new account first");
|
Toaster.info("Not logged in", "Please login or create a new account first");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -43,8 +44,8 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onFABTapped() {
|
void _onFABTapped() {
|
||||||
final userAcc = Provider.of<UserAccount>(context, listen: false);
|
final userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
if (userAcc.auth == null) {
|
if (!userAcc.isAuth()) {
|
||||||
Toaster.info("Not logged in", "Please login or create a new account first");
|
Toaster.info("Not logged in", "Please login or create a new account first");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -75,16 +76,10 @@ class _SCNNavLayoutState extends State<SCNNavLayout> {
|
|||||||
),
|
),
|
||||||
bottomNavigationBar: _buildNavBar(context),
|
bottomNavigationBar: _buildNavBar(context),
|
||||||
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked,
|
||||||
floatingActionButton: _buildFAB(context),
|
floatingActionButton: HidableFAB(
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildFAB(BuildContext context) {
|
|
||||||
return FloatingActionButton(
|
|
||||||
onPressed: _onFABTapped,
|
onPressed: _onFABTapped,
|
||||||
shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(17))),
|
icon: FontAwesomeIcons.solidPaperPlaneTop,
|
||||||
elevation: 2.0,
|
),
|
||||||
child: const Icon(FontAwesomeIcons.solidPaperPlaneTop),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,15 +1,13 @@
|
|||||||
import 'package:firebase_messaging/firebase_messaging.dart';
|
import 'package:firebase_messaging/firebase_messaging.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package: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/key_token_auth.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/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/user_account.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
class AccountRootPage extends StatefulWidget {
|
class AccountRootPage extends StatefulWidget {
|
||||||
@ -26,7 +24,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
late Future<int>? futureChannelAllCount;
|
late Future<int>? futureChannelAllCount;
|
||||||
late Future<int>? futureChannelSubscribedCount;
|
late Future<int>? futureChannelSubscribedCount;
|
||||||
|
|
||||||
late UserAccount userAcc;
|
late AppAuth userAcc;
|
||||||
|
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
|
||||||
@ -34,7 +32,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
userAcc = Provider.of<UserAccount>(context, listen: false);
|
userAcc = Provider.of<AppAuth>(context, listen: false);
|
||||||
userAcc.addListener(_onAuthStateChanged);
|
userAcc.addListener(_onAuthStateChanged);
|
||||||
_onAuthStateChanged();
|
_onAuthStateChanged();
|
||||||
}
|
}
|
||||||
@ -52,34 +50,34 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
futureChannelAllCount = null;
|
futureChannelAllCount = null;
|
||||||
futureChannelSubscribedCount = null;
|
futureChannelSubscribedCount = null;
|
||||||
|
|
||||||
if (userAcc.auth != null) {
|
if (userAcc.isAuth()) {
|
||||||
futureChannelAllCount = () async {
|
futureChannelAllCount = () async {
|
||||||
if (userAcc.auth == null) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.all);
|
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.all);
|
||||||
return channels.length;
|
return channels.length;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
futureChannelSubscribedCount = () async {
|
futureChannelSubscribedCount = () async {
|
||||||
if (userAcc.auth == null) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final channels = await APIClient.getChannelList(userAcc.auth!, ChannelSelector.subscribed);
|
final channels = await APIClient.getChannelList(userAcc, ChannelSelector.subscribed);
|
||||||
return channels.length;
|
return channels.length;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
futureSubscriptionCount = () async {
|
futureSubscriptionCount = () async {
|
||||||
if (userAcc.auth == null) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final subs = await APIClient.getSubscriptionList(userAcc.auth!);
|
final subs = await APIClient.getSubscriptionList(userAcc);
|
||||||
return subs.length;
|
return subs.length;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
futureClientCount = () async {
|
futureClientCount = () async {
|
||||||
if (userAcc.auth == null) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final clients = await APIClient.getClientList(userAcc.auth!);
|
final clients = await APIClient.getClientList(userAcc);
|
||||||
return clients.length;
|
return clients.length;
|
||||||
}();
|
}();
|
||||||
|
|
||||||
futureKeyCount = () async {
|
futureKeyCount = () async {
|
||||||
if (userAcc.auth == null) throw new Exception('not logged in');
|
if (!userAcc.isAuth()) throw new Exception('not logged in');
|
||||||
final keys = await APIClient.getKeyTokenList(userAcc.auth!);
|
final keys = await APIClient.getKeyTokenList(userAcc);
|
||||||
return keys.length;
|
return keys.length;
|
||||||
}();
|
}();
|
||||||
}
|
}
|
||||||
@ -87,13 +85,13 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Consumer<UserAccount>(
|
return Consumer<AppAuth>(
|
||||||
builder: (context, acc, child) {
|
builder: (context, acc, child) {
|
||||||
if (acc.auth == null) {
|
if (!userAcc.isAuth()) {
|
||||||
return _buildNoAuth(context);
|
return _buildNoAuth(context);
|
||||||
} else {
|
} else {
|
||||||
return FutureBuilder(
|
return FutureBuilder(
|
||||||
future: acc.loadUser(false),
|
future: acc.loadUser(force: false),
|
||||||
builder: ((context, snapshot) {
|
builder: ((context, snapshot) {
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
if (snapshot.hasError) {
|
if (snapshot.hasError) {
|
||||||
@ -165,7 +163,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildShowAccount(BuildContext context, UserAccount acc, User user) {
|
Widget _buildShowAccount(BuildContext context, AppAuth acc, User user) {
|
||||||
//TODO better layout
|
//TODO better layout
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
@ -446,7 +444,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
void _createNewAccount() async {
|
void _createNewAccount() async {
|
||||||
setState(() => loading = true);
|
setState(() => loading = true);
|
||||||
|
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final notificationSettings = await FirebaseMessaging.instance.requestPermission(provisional: true);
|
final notificationSettings = await FirebaseMessaging.instance.requestPermission(provisional: true);
|
||||||
@ -467,7 +465,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
|
|
||||||
final user = await APIClient.createUserWithClient(null, fcmToken, Globals().platform, Globals().version, Globals().hostname, Globals().clientType);
|
final user = await APIClient.createUserWithClient(null, fcmToken, Globals().platform, Globals().version, Globals().hostname, Globals().clientType);
|
||||||
|
|
||||||
acc.set(user.user, user.clients[0], KeyTokenAuth(userId: user.user.userID, tokenAdmin: user.adminKey, tokenSend: user.sendKey));
|
acc.set(user.user, user.clients[0], user.adminKey, user.sendKey);
|
||||||
|
|
||||||
await acc.save();
|
await acc.save();
|
||||||
Toaster.success("Success", 'Successfully Created a new account');
|
Toaster.success("Success", 'Successfully Created a new account');
|
||||||
@ -480,7 +478,7 @@ class _AccountRootPageState extends State<AccountRootPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _logout() async {
|
void _logout() async {
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
//TODO clear messages/channels/etc in open views
|
//TODO clear messages/channels/etc in open views
|
||||||
acc.clear();
|
acc.clear();
|
||||||
|
@ -4,10 +4,10 @@ import 'package:font_awesome_flutter/font_awesome_flutter.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/components/layout/scaffold.dart';
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||||
import 'package:simplecloudnotifier/models/key_token_auth.dart';
|
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
import 'package:simplecloudnotifier/state/user_account.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/token_source.dart';
|
||||||
import 'package:simplecloudnotifier/utils/toaster.dart';
|
import 'package:simplecloudnotifier/utils/toaster.dart';
|
||||||
|
|
||||||
class AccountLoginPage extends StatefulWidget {
|
class AccountLoginPage extends StatefulWidget {
|
||||||
@ -115,7 +115,7 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _login() async {
|
void _login() async {
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
setState(() => loading = true);
|
setState(() => loading = true);
|
||||||
@ -145,13 +145,11 @@ class _AccountLoginPageState extends State<AccountLoginPage> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final kta = KeyTokenAuth(userId: uid, tokenAdmin: atokv, tokenSend: stokv);
|
final user = await APIClient.getUser(DirectTokenSource(uid, atokv), uid);
|
||||||
|
|
||||||
final user = await APIClient.getUser(kta, uid);
|
final client = await APIClient.addClient(DirectTokenSource(uid, atokv), fcmToken, Globals().deviceModel, Globals().version, Globals().hostname, Globals().clientType);
|
||||||
|
|
||||||
final client = await APIClient.addClient(kta, fcmToken, Globals().deviceModel, Globals().version, Globals().hostname, Globals().clientType);
|
acc.set(user, client, atokv, stokv);
|
||||||
|
|
||||||
acc.set(user, client, kta);
|
|
||||||
await acc.save();
|
await acc.save();
|
||||||
|
|
||||||
Toaster.success("Login", "Successfully logged in");
|
Toaster.success("Login", "Successfully logged in");
|
||||||
|
@ -4,7 +4,7 @@ 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/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/user_account.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';
|
||||||
|
|
||||||
class ChannelRootPage extends StatefulWidget {
|
class ChannelRootPage extends StatefulWidget {
|
||||||
@ -17,8 +17,6 @@ class ChannelRootPage extends StatefulWidget {
|
|||||||
class _ChannelRootPageState extends State<ChannelRootPage> {
|
class _ChannelRootPageState extends State<ChannelRootPage> {
|
||||||
final PagingController<int, Channel> _pagingController = PagingController(firstPageKey: 0);
|
final PagingController<int, Channel> _pagingController = PagingController(firstPageKey: 0);
|
||||||
|
|
||||||
late UserAccount userAcc;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
_pagingController.addPageRequestListener((pageKey) {
|
_pagingController.addPageRequestListener((pageKey) {
|
||||||
@ -34,15 +32,15 @@ class _ChannelRootPageState extends State<ChannelRootPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPage(int pageKey) async {
|
Future<void> _fetchPage(int pageKey) async {
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
if (acc.auth == null) {
|
if (!acc.isAuth()) {
|
||||||
_pagingController.error = 'Not logged in';
|
_pagingController.error = 'Not logged in';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final items = (await APIClient.getChannelList(acc.auth!, ChannelSelector.all)).map((p) => p.channel).toList();
|
final items = (await APIClient.getChannelList(acc, ChannelSelector.all)).map((p) => p.channel).toList();
|
||||||
|
|
||||||
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? ''));
|
items.sort((a, b) => -1 * (a.timestampLastSent ?? '').compareTo(b.timestampLastSent ?? ''));
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ 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/state/user_account.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
|
||||||
class ChannelListItem extends StatefulWidget {
|
class ChannelListItem extends StatefulWidget {
|
||||||
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
||||||
@ -29,11 +29,11 @@ class _ChannelListItemState extends State<ChannelListItem> {
|
|||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
|
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
if (acc.auth != null) {
|
if (acc.isAuth()) {
|
||||||
() async {
|
() async {
|
||||||
final (_, channelMessages) = await APIClient.getMessageList(acc.auth!, '@start', pageSize: 1, channelIDs: [widget.channel.channelID]);
|
final (_, channelMessages) = await APIClient.getMessageList(acc, '@start', pageSize: 1, channelIDs: [widget.channel.channelID]);
|
||||||
setState(() {
|
setState(() {
|
||||||
lastMessage = channelMessages.firstOrNull;
|
lastMessage = channelMessages.firstOrNull;
|
||||||
});
|
});
|
||||||
|
@ -6,7 +6,7 @@ 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/application_log.dart';
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
import 'package:simplecloudnotifier/state/user_account.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';
|
||||||
|
|
||||||
class MessageListPage extends StatefulWidget {
|
class MessageListPage extends StatefulWidget {
|
||||||
@ -38,20 +38,20 @@ class _MessageListPageState extends State<MessageListPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _fetchPage(String thisPageToken) async {
|
Future<void> _fetchPage(String thisPageToken) async {
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
if (acc.auth == null) {
|
if (!acc.isAuth()) {
|
||||||
_pagingController.error = 'Not logged in';
|
_pagingController.error = 'Not logged in';
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (_channels == null) {
|
if (_channels == null) {
|
||||||
final channels = await APIClient.getChannelList(acc.auth!, 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};
|
||||||
}
|
}
|
||||||
|
|
||||||
final (npt, newItems) = await APIClient.getMessageList(acc.auth!, thisPageToken, pageSize: _pageSize);
|
final (npt, newItems) = await APIClient.getMessageList(acc, thisPageToken, pageSize: _pageSize);
|
||||||
|
|
||||||
if (npt == '@end') {
|
if (npt == '@end') {
|
||||||
_pagingController.appendLastPage(newItems);
|
_pagingController.appendLastPage(newItems);
|
||||||
|
@ -3,7 +3,7 @@ import 'package:provider/provider.dart';
|
|||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||||
import 'package:simplecloudnotifier/models/message.dart';
|
import 'package:simplecloudnotifier/models/message.dart';
|
||||||
import 'package:simplecloudnotifier/state/user_account.dart';
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
|
||||||
class MessageViewPage extends StatefulWidget {
|
class MessageViewPage extends StatefulWidget {
|
||||||
const MessageViewPage({super.key, required this.message});
|
const MessageViewPage({super.key, required this.message});
|
||||||
@ -24,9 +24,9 @@ class _MessageViewPageState extends State<MessageViewPage> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Future<Message> fetchMessage() async {
|
Future<Message> fetchMessage() async {
|
||||||
final acc = Provider.of<UserAccount>(context, listen: false);
|
final acc = Provider.of<AppAuth>(context, listen: false);
|
||||||
|
|
||||||
return await APIClient.getMessage(acc.auth!, widget.message.messageID);
|
return await APIClient.getMessage(acc, widget.message.messageID);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -1,159 +0,0 @@
|
|||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
|
||||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
|
||||||
import 'package:url_launcher/url_launcher.dart';
|
|
||||||
import 'package:simplecloudnotifier/state/user_account.dart';
|
|
||||||
|
|
||||||
class SendRootPage extends StatefulWidget {
|
|
||||||
const SendRootPage({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<SendRootPage> createState() => _SendRootPageState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SendRootPageState extends State<SendRootPage> {
|
|
||||||
late TextEditingController _msgTitle;
|
|
||||||
late TextEditingController _msgContent;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
_msgTitle = TextEditingController();
|
|
||||||
_msgContent = TextEditingController();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void dispose() {
|
|
||||||
_msgTitle.dispose();
|
|
||||||
_msgContent.dispose();
|
|
||||||
super.dispose();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
return Consumer<UserAccount>(
|
|
||||||
builder: (context, acc, child) {
|
|
||||||
return Padding(
|
|
||||||
padding: const EdgeInsets.all(16.0),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
_buildQRCode(context, acc),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FractionallySizedBox(
|
|
||||||
widthFactor: 1.0,
|
|
||||||
child: TextField(
|
|
||||||
controller: _msgTitle,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
labelText: 'Title',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FractionallySizedBox(
|
|
||||||
widthFactor: 1.0,
|
|
||||||
child: TextField(
|
|
||||||
controller: _msgContent,
|
|
||||||
decoration: const InputDecoration(
|
|
||||||
border: OutlineInputBorder(),
|
|
||||||
labelText: 'Text',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 16),
|
|
||||||
FilledButton(
|
|
||||||
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
|
||||||
onPressed: _send,
|
|
||||||
child: const Text('Send'),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _send() {
|
|
||||||
//...
|
|
||||||
}
|
|
||||||
|
|
||||||
Widget _buildQRCode(BuildContext context, UserAccount acc) {
|
|
||||||
if (acc.auth == null) {
|
|
||||||
return const Placeholder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (acc.user == null) {
|
|
||||||
return FutureBuilder(
|
|
||||||
future: acc.loadUser(false),
|
|
||||||
builder: ((context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
if (snapshot.hasError) {
|
|
||||||
return Text('Error: ${snapshot.error}'); //TODO better error display
|
|
||||||
}
|
|
||||||
var url = 'https://simplecloudnotifier.de?preset_user_id=${acc.user!.userID}&preset_user_key=${acc.auth!.tokenSend}';
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
_openWeb(url);
|
|
||||||
},
|
|
||||||
child: QrImageView(
|
|
||||||
data: url,
|
|
||||||
version: QrVersions.auto,
|
|
||||||
size: 400.0,
|
|
||||||
eyeStyle: QrEyeStyle(
|
|
||||||
eyeShape: QrEyeShape.square,
|
|
||||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
|
||||||
),
|
|
||||||
dataModuleStyle: QrDataModuleStyle(
|
|
||||||
dataModuleShape: QrDataModuleShape.square,
|
|
||||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return const SizedBox(
|
|
||||||
width: 400.0,
|
|
||||||
height: 400.0,
|
|
||||||
child: Center(child: CircularProgressIndicator()),
|
|
||||||
);
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
var url = 'https://simplecloudnotifier.de?preset_user_id=${acc.user!.userID}&preset_user_key=${acc.auth!.tokenSend}';
|
|
||||||
|
|
||||||
return GestureDetector(
|
|
||||||
onTap: () {
|
|
||||||
_openWeb(url);
|
|
||||||
},
|
|
||||||
child: QrImageView(
|
|
||||||
data: url,
|
|
||||||
version: QrVersions.auto,
|
|
||||||
size: 400.0,
|
|
||||||
eyeStyle: QrEyeStyle(
|
|
||||||
eyeShape: QrEyeShape.square,
|
|
||||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
|
||||||
),
|
|
||||||
dataModuleStyle: QrDataModuleStyle(
|
|
||||||
dataModuleShape: QrDataModuleShape.square,
|
|
||||||
color: Theme.of(context).textTheme.bodyLarge?.color,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _openWeb(String url) async {
|
|
||||||
try {
|
|
||||||
final Uri uri = Uri.parse(url);
|
|
||||||
|
|
||||||
if (await canLaunchUrl(uri)) {
|
|
||||||
await launchUrl(uri);
|
|
||||||
} else {
|
|
||||||
// TODO ("Cannot open URL");
|
|
||||||
}
|
|
||||||
} catch (exc, trace) {
|
|
||||||
ApplicationLog.error('Failed to open URL: ' + exc.toString(), additional: 'URL: ${url}', trace: trace);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
142
flutter/lib/pages/send/send.dart
Normal file
142
flutter/lib/pages/send/send.dart
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||||
|
import 'package:url_launcher/url_launcher.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/app_auth.dart';
|
||||||
|
|
||||||
|
class SendRootPage extends StatefulWidget {
|
||||||
|
const SendRootPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<SendRootPage> createState() => _SendRootPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SendRootPageState extends State<SendRootPage> {
|
||||||
|
late TextEditingController _msgTitle;
|
||||||
|
late TextEditingController _msgContent;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_msgTitle = TextEditingController();
|
||||||
|
_msgContent = TextEditingController();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_msgTitle.dispose();
|
||||||
|
_msgContent.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<AppAuth>(
|
||||||
|
builder: (context, acc, child) {
|
||||||
|
return SingleChildScrollView(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(16.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
_buildQRCode(context, acc),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: TextField(
|
||||||
|
controller: _msgTitle,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
labelText: 'Title',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FractionallySizedBox(
|
||||||
|
widthFactor: 1.0,
|
||||||
|
child: TextField(
|
||||||
|
controller: _msgContent,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
labelText: 'Text',
|
||||||
|
),
|
||||||
|
minLines: 2,
|
||||||
|
maxLines: null,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
FilledButton(
|
||||||
|
style: FilledButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
|
||||||
|
onPressed: _send,
|
||||||
|
child: const Text('Send'),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 32),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _send() {
|
||||||
|
//...
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _buildQRCode(BuildContext context, AppAuth acc) {
|
||||||
|
if (!acc.isAuth()) {
|
||||||
|
return const Placeholder();
|
||||||
|
}
|
||||||
|
|
||||||
|
return FutureBuilder(
|
||||||
|
future: acc.loadUser(force: false),
|
||||||
|
builder: ((context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.hasError) {
|
||||||
|
return Text('Error: ${snapshot.error}'); //TODO better error display
|
||||||
|
}
|
||||||
|
var url = (acc.tokenSend == null) ? 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}' : 'https://simplecloudnotifier.de?preset_user_id=${acc.userID}&preset_user_key=${acc.tokenSend}';
|
||||||
|
return GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
_openWeb(url);
|
||||||
|
},
|
||||||
|
child: QrImageView(
|
||||||
|
data: url,
|
||||||
|
version: QrVersions.auto,
|
||||||
|
size: 300.0,
|
||||||
|
eyeStyle: QrEyeStyle(
|
||||||
|
eyeShape: QrEyeShape.square,
|
||||||
|
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||||
|
),
|
||||||
|
dataModuleStyle: QrDataModuleStyle(
|
||||||
|
dataModuleShape: QrDataModuleShape.square,
|
||||||
|
color: Theme.of(context).textTheme.bodyLarge?.color,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return const SizedBox(
|
||||||
|
width: 300.0,
|
||||||
|
height: 300.0,
|
||||||
|
child: Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _openWeb(String url) async {
|
||||||
|
try {
|
||||||
|
final Uri uri = Uri.parse(url);
|
||||||
|
|
||||||
|
if (await canLaunchUrl(uri)) {
|
||||||
|
await launchUrl(uri);
|
||||||
|
} else {
|
||||||
|
// TODO ("Cannot open URL");
|
||||||
|
}
|
||||||
|
} catch (exc, trace) {
|
||||||
|
ApplicationLog.error('Failed to open URL: ' + exc.toString(), additional: 'URL: ${url}', trace: trace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
157
flutter/lib/state/app_auth.dart
Normal file
157
flutter/lib/state/app_auth.dart
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:shared_preferences/shared_preferences.dart';
|
||||||
|
import 'package:simplecloudnotifier/api/api_client.dart';
|
||||||
|
import 'package:simplecloudnotifier/api/api_exception.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/client.dart';
|
||||||
|
import 'package:simplecloudnotifier/models/user.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/globals.dart';
|
||||||
|
import 'package:simplecloudnotifier/state/token_source.dart';
|
||||||
|
|
||||||
|
class AppAuth extends ChangeNotifier implements TokenSource {
|
||||||
|
String? _clientID;
|
||||||
|
String? _userID;
|
||||||
|
String? _tokenAdmin;
|
||||||
|
String? _tokenSend;
|
||||||
|
|
||||||
|
User? _user;
|
||||||
|
Client? _client;
|
||||||
|
|
||||||
|
String? get userID => _userID;
|
||||||
|
String? get tokenAdmin => _tokenAdmin;
|
||||||
|
String? get tokenSend => _tokenSend;
|
||||||
|
|
||||||
|
static AppAuth? _singleton = AppAuth._internal();
|
||||||
|
|
||||||
|
factory AppAuth() {
|
||||||
|
return _singleton ?? (_singleton = AppAuth._internal());
|
||||||
|
}
|
||||||
|
|
||||||
|
AppAuth._internal() {
|
||||||
|
load();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isAuth() {
|
||||||
|
return _userID != null && _tokenAdmin != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set(User user, Client client, String tokenAdmin, String tokenSend) {
|
||||||
|
_client = client;
|
||||||
|
_user = user;
|
||||||
|
_userID = user.userID;
|
||||||
|
_clientID = client.clientID;
|
||||||
|
_tokenAdmin = tokenAdmin;
|
||||||
|
_tokenSend = tokenSend;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void setClientAndClientID(Client client) {
|
||||||
|
_client = client;
|
||||||
|
_clientID = client.clientID;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void clear() {
|
||||||
|
_clientID = null;
|
||||||
|
_userID = null;
|
||||||
|
_tokenAdmin = null;
|
||||||
|
_tokenSend = null;
|
||||||
|
|
||||||
|
_client = null;
|
||||||
|
_user = null;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void load() {
|
||||||
|
final uid = Globals().sharedPrefs.getString('auth.userid');
|
||||||
|
final cid = Globals().sharedPrefs.getString('auth.clientid');
|
||||||
|
final toka = Globals().sharedPrefs.getString('auth.tokenadmin');
|
||||||
|
final toks = Globals().sharedPrefs.getString('auth.tokensend');
|
||||||
|
|
||||||
|
if (uid == null || toka == null || toks == null || cid == null) {
|
||||||
|
clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_clientID = cid;
|
||||||
|
_userID = uid;
|
||||||
|
_tokenAdmin = toka;
|
||||||
|
_tokenSend = toks;
|
||||||
|
|
||||||
|
_client = null;
|
||||||
|
_user = null;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> save() async {
|
||||||
|
final prefs = await SharedPreferences.getInstance();
|
||||||
|
if (_clientID == null || _userID == null || _tokenAdmin == null || _tokenSend == null) {
|
||||||
|
await prefs.remove('auth.userid');
|
||||||
|
await prefs.remove('auth.tokenadmin');
|
||||||
|
await prefs.remove('auth.tokensend');
|
||||||
|
} else {
|
||||||
|
await prefs.setString('auth.userid', _userID!);
|
||||||
|
await prefs.setString('auth.clientid', _clientID!);
|
||||||
|
await prefs.setString('auth.tokenadmin', _tokenAdmin!);
|
||||||
|
await prefs.setString('auth.tokensend', _tokenSend!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<User> loadUser({bool force = false}) async {
|
||||||
|
if (!force && _user != null && _user!.userID == _userID) {
|
||||||
|
return _user!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_userID == null || _tokenAdmin == null) {
|
||||||
|
throw Exception('Not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
final user = await APIClient.getUser(this, _userID!);
|
||||||
|
|
||||||
|
_user = user;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
await save();
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Client?> loadClient({bool force = false}) async {
|
||||||
|
if (!force && _client != null && _client!.clientID == _clientID) {
|
||||||
|
return _client!;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_clientID == null || _tokenAdmin == null) {
|
||||||
|
throw Exception('Not authenticated');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final client = await APIClient.getClient(this, _clientID!);
|
||||||
|
|
||||||
|
_client = client;
|
||||||
|
notifyListeners();
|
||||||
|
|
||||||
|
await save();
|
||||||
|
|
||||||
|
return client;
|
||||||
|
} on APIException catch (_) {
|
||||||
|
_client = null;
|
||||||
|
notifyListeners();
|
||||||
|
return null;
|
||||||
|
} catch (exc) {
|
||||||
|
_client = null;
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getToken() {
|
||||||
|
return _tokenAdmin!;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getUserID() {
|
||||||
|
return _userID!;
|
||||||
|
}
|
||||||
|
}
|
21
flutter/lib/state/token_source.dart
Normal file
21
flutter/lib/state/token_source.dart
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
abstract class TokenSource {
|
||||||
|
String getToken();
|
||||||
|
String getUserID();
|
||||||
|
}
|
||||||
|
|
||||||
|
class DirectTokenSource implements TokenSource {
|
||||||
|
final String _userID;
|
||||||
|
final String _token;
|
||||||
|
|
||||||
|
DirectTokenSource(this._userID, this._token);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getUserID() {
|
||||||
|
return _userID;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String getToken() {
|
||||||
|
return _token;
|
||||||
|
}
|
||||||
|
}
|
@ -1,117 +0,0 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:shared_preferences/shared_preferences.dart';
|
|
||||||
import 'package:simplecloudnotifier/api/api_client.dart';
|
|
||||||
import 'package:simplecloudnotifier/models/client.dart';
|
|
||||||
import 'package:simplecloudnotifier/models/key_token_auth.dart';
|
|
||||||
import 'package:simplecloudnotifier/models/user.dart';
|
|
||||||
import 'package:simplecloudnotifier/state/globals.dart';
|
|
||||||
|
|
||||||
class UserAccount extends ChangeNotifier {
|
|
||||||
User? _user;
|
|
||||||
User? get user => _user;
|
|
||||||
|
|
||||||
Client? _client;
|
|
||||||
Client? get client => _client;
|
|
||||||
|
|
||||||
KeyTokenAuth? _auth;
|
|
||||||
KeyTokenAuth? get auth => _auth;
|
|
||||||
|
|
||||||
static UserAccount? _singleton = UserAccount._internal();
|
|
||||||
|
|
||||||
factory UserAccount() {
|
|
||||||
return _singleton ?? (_singleton = UserAccount._internal());
|
|
||||||
}
|
|
||||||
|
|
||||||
UserAccount._internal() {
|
|
||||||
load();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setToken(KeyTokenAuth auth) {
|
|
||||||
_auth = auth;
|
|
||||||
_user = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearToken() {
|
|
||||||
_auth = null;
|
|
||||||
_user = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setUser(User user) {
|
|
||||||
_user = user;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearUser() {
|
|
||||||
_user = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void setClient(Client client) {
|
|
||||||
_client = client;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clearClient() {
|
|
||||||
_client = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void set(User user, Client client, KeyTokenAuth auth) {
|
|
||||||
_client = client;
|
|
||||||
_user = user;
|
|
||||||
_auth = auth;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
_client = null;
|
|
||||||
_user = null;
|
|
||||||
_auth = null;
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void load() {
|
|
||||||
final uid = Globals().sharedPrefs.getString('auth.userid');
|
|
||||||
final toka = Globals().sharedPrefs.getString('auth.tokenadmin');
|
|
||||||
final toks = Globals().sharedPrefs.getString('auth.tokensend');
|
|
||||||
|
|
||||||
if (uid != null && toka != null && toks != null) {
|
|
||||||
setToken(KeyTokenAuth(userId: uid, tokenAdmin: toka, tokenSend: toks));
|
|
||||||
} else {
|
|
||||||
clearToken();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> save() async {
|
|
||||||
final prefs = await SharedPreferences.getInstance();
|
|
||||||
if (_auth == null) {
|
|
||||||
await prefs.remove('auth.userid');
|
|
||||||
await prefs.remove('auth.tokenadmin');
|
|
||||||
await prefs.remove('auth.tokensend');
|
|
||||||
} else {
|
|
||||||
await prefs.setString('auth.userid', _auth!.userId);
|
|
||||||
await prefs.setString('auth.tokenadmin', _auth!.tokenAdmin);
|
|
||||||
await prefs.setString('auth.tokensend', _auth!.tokenSend);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<User> loadUser(bool force) async {
|
|
||||||
if (!force && _user != null) {
|
|
||||||
return _user!;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_auth == null) {
|
|
||||||
throw Exception('Not authenticated');
|
|
||||||
}
|
|
||||||
|
|
||||||
final user = await APIClient.getUser(_auth!, _auth!.userId);
|
|
||||||
|
|
||||||
setUser(user);
|
|
||||||
|
|
||||||
await save();
|
|
||||||
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user