Hive, requestlog, etc
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:fl_toast/fl_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:http/http.dart' as http;
|
||||
import 'package:simplecloudnotifier/models/api_error.dart';
|
||||
import 'package:simplecloudnotifier/models/key_token_auth.dart';
|
||||
import 'package:simplecloudnotifier/models/user.dart';
|
||||
import 'package:simplecloudnotifier/state/globals.dart';
|
||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||
|
||||
import '../models/channel.dart';
|
||||
import '../models/message.dart';
|
||||
@@ -21,69 +26,141 @@ enum ChannelSelector {
|
||||
class APIClient {
|
||||
static const String _base = 'https://simplecloudnotifier.de/api/v2';
|
||||
|
||||
static Future<bool> verifyToken(String uid, String tok) async {
|
||||
final uri = Uri.parse('$_base/users/$uid');
|
||||
final response = await http.get(uri, headers: {'Authorization': 'SCN $tok'});
|
||||
static Future<T> _request<T>({
|
||||
required String name,
|
||||
required String method,
|
||||
required String relURL,
|
||||
Map<String, String>? query,
|
||||
required T Function(Map<String, dynamic> json)? fn,
|
||||
dynamic jsonBody,
|
||||
KeyTokenAuth? auth,
|
||||
Map<String, String>? header,
|
||||
}) async {
|
||||
final t0 = DateTime.now();
|
||||
|
||||
return (response.statusCode == 200);
|
||||
}
|
||||
final uri = Uri.parse('$_base/$relURL').replace(queryParameters: query ?? {});
|
||||
|
||||
static Future<User> getUser(String uid, String tok) async {
|
||||
final uri = Uri.parse('$_base/users/$uid');
|
||||
final response = await http.get(uri, headers: {'Authorization': 'SCN $tok'});
|
||||
final req = http.Request(method, uri);
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('API request failed');
|
||||
if (jsonBody != null) {
|
||||
req.body = jsonEncode(jsonBody);
|
||||
req.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
|
||||
return User.fromJson(jsonDecode(response.body));
|
||||
if (auth != null) {
|
||||
req.headers['Authorization'] = 'SCN ${auth.token}';
|
||||
}
|
||||
|
||||
req.headers['User-Agent'] = 'simplecloudnotifier/flutter/${Globals().platform.replaceAll(' ', '_')} ${Globals().version}+${Globals().buildNumber}';
|
||||
|
||||
if (header != null && !header.isEmpty) {
|
||||
req.headers.addAll(header);
|
||||
}
|
||||
|
||||
int responseStatusCode = 0;
|
||||
String responseBody = '';
|
||||
Map<String, String> responseHeaders = {};
|
||||
|
||||
try {
|
||||
final response = await req.send();
|
||||
responseBody = await response.stream.bytesToString();
|
||||
responseStatusCode = response.statusCode;
|
||||
responseHeaders = response.headers;
|
||||
} catch (exc, trace) {
|
||||
RequestLog.addRequestException(name, t0, method, uri, req.body, req.headers, exc, trace);
|
||||
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
|
||||
rethrow;
|
||||
}
|
||||
|
||||
if (responseStatusCode != 200) {
|
||||
try {
|
||||
final apierr = APIError.fromJson(jsonDecode(responseBody));
|
||||
|
||||
RequestLog.addRequestAPIError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, apierr);
|
||||
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
|
||||
throw Exception(apierr.message);
|
||||
} catch (_) {}
|
||||
|
||||
RequestLog.addRequestErrorStatuscode(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
|
||||
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
|
||||
throw Exception('API request failed with status code ${responseStatusCode}');
|
||||
}
|
||||
|
||||
try {
|
||||
final data = jsonDecode(responseBody);
|
||||
|
||||
if (fn != null) {
|
||||
final result = fn(data);
|
||||
RequestLog.addRequestSuccess(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
|
||||
return result;
|
||||
} else {
|
||||
RequestLog.addRequestSuccess(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders);
|
||||
return null as T;
|
||||
}
|
||||
} catch (exc, trace) {
|
||||
RequestLog.addRequestDecodeError(name, t0, method, uri, req.body, req.headers, responseStatusCode, responseBody, responseHeaders, exc, trace);
|
||||
showPlatformToast(child: Text('Request "${name}" is fehlgeschlagen'), context: ToastProvider.context);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
// ==========================================================================================================================================================
|
||||
|
||||
static Future<bool> verifyToken(String uid, String tok) async {
|
||||
try {
|
||||
await _request<void>(
|
||||
name: 'verifyToken',
|
||||
method: 'GET',
|
||||
relURL: '/users/$uid',
|
||||
fn: null,
|
||||
auth: KeyTokenAuth(userId: uid, token: tok),
|
||||
);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
static Future<User> getUser(KeyTokenAuth auth, String uid) async {
|
||||
return await _request(
|
||||
name: 'getUser',
|
||||
method: 'GET',
|
||||
relURL: 'users/$uid',
|
||||
fn: User.fromJson,
|
||||
auth: auth,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<List<ChannelWithSubscription>> getChannelList(KeyTokenAuth auth, ChannelSelector sel) async {
|
||||
var url = '$_base/users/${auth.userId}/channels?selector=${sel.apiKey}';
|
||||
final uri = Uri.parse(url);
|
||||
|
||||
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'});
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('API request failed');
|
||||
}
|
||||
|
||||
final data = jsonDecode(response.body);
|
||||
|
||||
return data['channels'].map<ChannelWithSubscription>((e) => ChannelWithSubscription.fromJson(e)).toList() as List<ChannelWithSubscription>;
|
||||
return await _request(
|
||||
name: 'getChannelList',
|
||||
method: 'GET',
|
||||
relURL: 'users/${auth.userId}/channels',
|
||||
query: {'selector': sel.apiKey},
|
||||
fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels']),
|
||||
auth: auth,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<(String, List<Message>)> getMessageList(KeyTokenAuth auth, String pageToken, int? pageSize) async {
|
||||
var url = '$_base/messages?next_page_token=$pageToken';
|
||||
if (pageSize != null) {
|
||||
url += '&page_size=$pageSize';
|
||||
}
|
||||
final uri = Uri.parse(url);
|
||||
|
||||
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'});
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('API request failed');
|
||||
}
|
||||
|
||||
final data = jsonDecode(response.body);
|
||||
|
||||
final npt = data['next_page_token'] as String;
|
||||
|
||||
final messages = data['messages'].map<Message>((e) => Message.fromJson(e)).toList() as List<Message>;
|
||||
|
||||
return (npt, messages);
|
||||
return await _request(
|
||||
name: 'getMessageList',
|
||||
method: 'GET',
|
||||
relURL: 'messages',
|
||||
query: {'next_page_token': pageToken, if (pageSize != null) 'page_size': pageSize.toString()},
|
||||
fn: (json) => Message.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
|
||||
auth: auth,
|
||||
);
|
||||
}
|
||||
|
||||
static Future<Message> getMessage(KeyTokenAuth auth, String msgid) async {
|
||||
final uri = Uri.parse('$_base/messages/$msgid');
|
||||
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'});
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
throw Exception('API request failed');
|
||||
}
|
||||
|
||||
return Message.fromJson(jsonDecode(response.body));
|
||||
return await _request(
|
||||
name: 'getMessage',
|
||||
method: 'GET',
|
||||
relURL: 'messages/$msgid',
|
||||
query: {},
|
||||
fn: Message.fromJson,
|
||||
auth: auth,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1,13 +1,34 @@
|
||||
import 'package:fl_toast/fl_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:simplecloudnotifier/state/database.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
import 'package:simplecloudnotifier/nav_layout.dart';
|
||||
import 'package:simplecloudnotifier/state/app_theme.dart';
|
||||
import 'package:simplecloudnotifier/state/application_log.dart';
|
||||
import 'package:simplecloudnotifier/state/globals.dart';
|
||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||
import 'package:simplecloudnotifier/state/user_account.dart';
|
||||
|
||||
void main() async {
|
||||
await SCNDatabase.create();
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Hive.initFlutter();
|
||||
await Globals().init();
|
||||
|
||||
Hive.registerAdapter(SCNRequestAdapter());
|
||||
Hive.registerAdapter(SCNLogAdapter());
|
||||
|
||||
try {
|
||||
await Hive.openBox<SCNRequest>('scn-requests');
|
||||
await Hive.openBox<SCNLog>('scn-logs');
|
||||
} catch (e) {
|
||||
print(e);
|
||||
Hive.deleteBoxFromDisk('scn-requests');
|
||||
Hive.deleteBoxFromDisk('scn-logs');
|
||||
await Hive.openBox<SCNRequest>('scn-requests');
|
||||
await Hive.openBox<SCNLog>('scn-logs');
|
||||
}
|
||||
|
||||
runApp(
|
||||
MultiProvider(
|
||||
@@ -31,7 +52,7 @@ class SCNApp extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Provider.of<UserAccount>(context); // ensure UserAccount is loaded
|
||||
Provider.of<UserAccount>(context); // ensure UserAccount is loaded (unneccessary if lazy: false is set in MultiProvider ??)
|
||||
|
||||
return Consumer<AppTheme>(
|
||||
builder: (context, appTheme, child) => MaterialApp(
|
||||
@@ -40,7 +61,7 @@ class SCNApp extends StatelessWidget {
|
||||
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const SCNNavLayout(),
|
||||
home: const ToastProvider(child: SCNNavLayout()),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
22
flutter/lib/models/api_error.dart
Normal file
22
flutter/lib/models/api_error.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
class APIError {
|
||||
final String success;
|
||||
final String error;
|
||||
final String errhighlight;
|
||||
final String message;
|
||||
|
||||
const APIError({
|
||||
required this.success,
|
||||
required this.error,
|
||||
required this.errhighlight,
|
||||
required this.message,
|
||||
});
|
||||
|
||||
factory APIError.fromJson(Map<String, dynamic> json) {
|
||||
return APIError(
|
||||
success: json['success'],
|
||||
error: json['error'],
|
||||
errhighlight: json['errhighlight'],
|
||||
message: json['message'],
|
||||
);
|
||||
}
|
||||
}
|
@@ -24,31 +24,17 @@ class Channel {
|
||||
});
|
||||
|
||||
factory Channel.fromJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{
|
||||
'channel_id': String channelID,
|
||||
'owner_user_id': String ownerUserID,
|
||||
'internal_name': String internalName,
|
||||
'display_name': String displayName,
|
||||
'description_name': String? descriptionName,
|
||||
'subscribe_key': String? subscribeKey,
|
||||
'timestamp_created': String timestampCreated,
|
||||
'timestamp_lastsent': String? timestampLastSent,
|
||||
'messages_sent': int messagesSent,
|
||||
} =>
|
||||
Channel(
|
||||
channelID: channelID,
|
||||
ownerUserID: ownerUserID,
|
||||
internalName: internalName,
|
||||
displayName: displayName,
|
||||
descriptionName: descriptionName,
|
||||
subscribeKey: subscribeKey,
|
||||
timestampCreated: timestampCreated,
|
||||
timestampLastSent: timestampLastSent,
|
||||
messagesSent: messagesSent,
|
||||
),
|
||||
_ => throw const FormatException('Failed to decode Channel.'),
|
||||
};
|
||||
return Channel(
|
||||
channelID: json['channel_id'],
|
||||
ownerUserID: json['owner_user_id'],
|
||||
internalName: json['internal_name'],
|
||||
displayName: json['display_name'],
|
||||
descriptionName: json['description_name'],
|
||||
subscribeKey: json['subscribe_key'],
|
||||
timestampCreated: json['timestamp_created'],
|
||||
timestampLastSent: json['timestamp_lastsent'],
|
||||
messagesSent: json['messages_sent'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,32 +55,21 @@ class ChannelWithSubscription extends Channel {
|
||||
});
|
||||
|
||||
factory ChannelWithSubscription.fromJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{
|
||||
'channel_id': String channelID,
|
||||
'owner_user_id': String ownerUserID,
|
||||
'internal_name': String internalName,
|
||||
'display_name': String displayName,
|
||||
'description_name': String? descriptionName,
|
||||
'subscribe_key': String? subscribeKey,
|
||||
'timestamp_created': String timestampCreated,
|
||||
'timestamp_lastsent': String? timestampLastSent,
|
||||
'messages_sent': int messagesSent,
|
||||
'subscription': dynamic subscription,
|
||||
} =>
|
||||
ChannelWithSubscription(
|
||||
channelID: channelID,
|
||||
ownerUserID: ownerUserID,
|
||||
internalName: internalName,
|
||||
displayName: displayName,
|
||||
descriptionName: descriptionName,
|
||||
subscribeKey: subscribeKey,
|
||||
timestampCreated: timestampCreated,
|
||||
timestampLastSent: timestampLastSent,
|
||||
messagesSent: messagesSent,
|
||||
subscription: Subscription.fromJson(subscription),
|
||||
),
|
||||
_ => throw const FormatException('Failed to decode Channel.'),
|
||||
};
|
||||
return ChannelWithSubscription(
|
||||
channelID: json['channel_id'],
|
||||
ownerUserID: json['owner_user_id'],
|
||||
internalName: json['internal_name'],
|
||||
displayName: json['display_name'],
|
||||
descriptionName: json['description_name'],
|
||||
subscribeKey: json['subscribe_key'],
|
||||
timestampCreated: json['timestamp_created'],
|
||||
timestampLastSent: json['timestamp_lastsent'],
|
||||
messagesSent: json['messages_sent'],
|
||||
subscription: Subscription.fromJson(json['subscription']),
|
||||
);
|
||||
}
|
||||
|
||||
static List<ChannelWithSubscription> fromJsonArray(List<dynamic> jsonArr) {
|
||||
return jsonArr.map<ChannelWithSubscription>((e) => ChannelWithSubscription.fromJson(e)).toList();
|
||||
}
|
||||
}
|
||||
|
@@ -30,38 +30,28 @@ class Message {
|
||||
});
|
||||
|
||||
factory Message.fromJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{
|
||||
'message_id': String messageID,
|
||||
'sender_user_id': String senderUserID,
|
||||
'channel_internal_name': String channelInternalName,
|
||||
'channel_id': String channelID,
|
||||
'sender_name': String? senderName,
|
||||
'sender_ip': String senderIP,
|
||||
'timestamp': String timestamp,
|
||||
'title': String title,
|
||||
'content': String? content,
|
||||
'priority': int priority,
|
||||
'usr_message_id': String? userMessageID,
|
||||
'used_key_id': String usedKeyID,
|
||||
'trimmed': bool trimmed,
|
||||
} =>
|
||||
Message(
|
||||
messageID: messageID,
|
||||
senderUserID: senderUserID,
|
||||
channelInternalName: channelInternalName,
|
||||
channelID: channelID,
|
||||
senderName: senderName,
|
||||
senderIP: senderIP,
|
||||
timestamp: timestamp,
|
||||
title: title,
|
||||
content: content,
|
||||
priority: priority,
|
||||
userMessageID: userMessageID,
|
||||
usedKeyID: usedKeyID,
|
||||
trimmed: trimmed,
|
||||
),
|
||||
_ => throw const FormatException('Failed to decode Message.'),
|
||||
};
|
||||
return Message(
|
||||
messageID: json['message_id'],
|
||||
senderUserID: json['sender_user_id'],
|
||||
channelInternalName: json['channel_internal_name'],
|
||||
channelID: json['channel_id'],
|
||||
senderName: json['sender_name'],
|
||||
senderIP: json['sender_ip'],
|
||||
timestamp: json['timestamp'],
|
||||
title: json['title'],
|
||||
content: json['content'],
|
||||
priority: json['priority'],
|
||||
userMessageID: json['usr_message_id'],
|
||||
usedKeyID: json['used_key_id'],
|
||||
trimmed: json['trimmed'],
|
||||
);
|
||||
}
|
||||
|
||||
static fromPaginatedJsonArray(Map<String, dynamic> data, String keyMessages, String keyToken) {
|
||||
final npt = data[keyToken] as String;
|
||||
|
||||
final messages = (data[keyMessages] as List<dynamic>).map<Message>((e) => Message.fromJson(e)).toList();
|
||||
|
||||
return (npt, messages);
|
||||
}
|
||||
}
|
||||
|
@@ -18,26 +18,14 @@ class Subscription {
|
||||
});
|
||||
|
||||
factory Subscription.fromJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{
|
||||
'subscription_id': String subscriptionID,
|
||||
'subscriber_user_id': String subscriberUserID,
|
||||
'channel_owner_user_id': String channelOwnerUserID,
|
||||
'channel_id': String channelID,
|
||||
'channel_internal_name': String channelInternalName,
|
||||
'timestamp_created': String timestampCreated,
|
||||
'confirmed': bool confirmed,
|
||||
} =>
|
||||
Subscription(
|
||||
subscriptionID: subscriptionID,
|
||||
subscriberUserID: subscriberUserID,
|
||||
channelOwnerUserID: channelOwnerUserID,
|
||||
channelID: channelID,
|
||||
channelInternalName: channelInternalName,
|
||||
timestampCreated: timestampCreated,
|
||||
confirmed: confirmed,
|
||||
),
|
||||
_ => throw const FormatException('Failed to decode Subscription.'),
|
||||
};
|
||||
return Subscription(
|
||||
subscriptionID: json['subscription_id'],
|
||||
subscriberUserID: json['subscriber_user_id'],
|
||||
channelOwnerUserID: json['channel_owner_user_id'],
|
||||
channelID: json['channel_id'],
|
||||
channelInternalName: json['channel_internal_name'],
|
||||
timestampCreated: json['timestamp_created'],
|
||||
confirmed: json['confirmed'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -40,48 +40,25 @@ class User {
|
||||
});
|
||||
|
||||
factory User.fromJson(Map<String, dynamic> json) {
|
||||
return switch (json) {
|
||||
{
|
||||
'user_id': String userID,
|
||||
'username': String? username,
|
||||
'timestamp_created': String timestampCreated,
|
||||
'timestamp_lastread': String? timestampLastRead,
|
||||
'timestamp_lastsent': String? timestampLastSent,
|
||||
'messages_sent': int messagesSent,
|
||||
'quota_used': int quotaUsed,
|
||||
'quota_remaining': int quotaRemaining,
|
||||
'quota_max': int quotaPerDay,
|
||||
'is_pro': bool isPro,
|
||||
'default_channel': String defaultChannel,
|
||||
'max_body_size': int maxBodySize,
|
||||
'max_title_length': int maxTitleLength,
|
||||
'default_priority': int defaultPriority,
|
||||
'max_channel_name_length': int maxChannelNameLength,
|
||||
'max_channel_description_length': int maxChannelDescriptionLength,
|
||||
'max_sender_name_length': int maxSenderNameLength,
|
||||
'max_user_message_id_length': int maxUserMessageIDLength,
|
||||
} =>
|
||||
User(
|
||||
userID: userID,
|
||||
username: username,
|
||||
timestampCreated: timestampCreated,
|
||||
timestampLastRead: timestampLastRead,
|
||||
timestampLastSent: timestampLastSent,
|
||||
messagesSent: messagesSent,
|
||||
quotaUsed: quotaUsed,
|
||||
quotaRemaining: quotaRemaining,
|
||||
quotaPerDay: quotaPerDay,
|
||||
isPro: isPro,
|
||||
defaultChannel: defaultChannel,
|
||||
maxBodySize: maxBodySize,
|
||||
maxTitleLength: maxTitleLength,
|
||||
defaultPriority: defaultPriority,
|
||||
maxChannelNameLength: maxChannelNameLength,
|
||||
maxChannelDescriptionLength: maxChannelDescriptionLength,
|
||||
maxSenderNameLength: maxSenderNameLength,
|
||||
maxUserMessageIDLength: maxUserMessageIDLength,
|
||||
),
|
||||
_ => throw const FormatException('Failed to decode User.'),
|
||||
};
|
||||
return User(
|
||||
userID: json['user_id'],
|
||||
username: json['username'],
|
||||
timestampCreated: json['timestamp_created'],
|
||||
timestampLastRead: json['timestamp_lastread'],
|
||||
timestampLastSent: json['timestamp_lastsent'],
|
||||
messagesSent: json['messages_sent'],
|
||||
quotaUsed: json['quota_used'],
|
||||
quotaRemaining: json['quota_remaining'],
|
||||
quotaPerDay: json['quota_max'],
|
||||
isPro: json['is_pro'],
|
||||
defaultChannel: json['default_channel'],
|
||||
maxBodySize: json['max_body_size'],
|
||||
maxTitleLength: json['max_title_length'],
|
||||
defaultPriority: json['default_priority'],
|
||||
maxChannelNameLength: json['max_channel_name_length'],
|
||||
maxChannelDescriptionLength: json['max_channel_description_length'],
|
||||
maxSenderNameLength: json['max_sender_name_length'],
|
||||
maxUserMessageIDLength: json['max_user_message_id_length'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
13
flutter/lib/pages/debug/debug_logs.dart
Normal file
13
flutter/lib/pages/debug/debug_logs.dart
Normal file
@@ -0,0 +1,13 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DebugLogsPage extends StatefulWidget {
|
||||
@override
|
||||
_DebugLogsPageState createState() => _DebugLogsPageState();
|
||||
}
|
||||
|
||||
class _DebugLogsPageState extends State<DebugLogsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(/* Add your UI components here */);
|
||||
}
|
||||
}
|
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||
import 'package:simplecloudnotifier/pages/debug/debug_colors.dart';
|
||||
import 'package:simplecloudnotifier/pages/debug/debug_logs.dart';
|
||||
import 'package:simplecloudnotifier/pages/debug/debug_persistence.dart';
|
||||
import 'package:simplecloudnotifier/pages/debug/debug_requests.dart';
|
||||
|
||||
@@ -9,13 +10,14 @@ class DebugMainPage extends StatefulWidget {
|
||||
_DebugMainPageState createState() => _DebugMainPageState();
|
||||
}
|
||||
|
||||
enum DebugMainPageSubPage { colors, requests, persistence }
|
||||
enum DebugMainPageSubPage { colors, requests, persistence, logs }
|
||||
|
||||
class _DebugMainPageState extends State<DebugMainPage> {
|
||||
final Map<DebugMainPageSubPage, Widget> _subpages = {
|
||||
DebugMainPageSubPage.colors: DebugColorsPage(),
|
||||
DebugMainPageSubPage.requests: DebugRequestsPage(),
|
||||
DebugMainPageSubPage.persistence: DebugPersistencePage(),
|
||||
DebugMainPageSubPage.logs: DebugLogsPage(),
|
||||
};
|
||||
|
||||
DebugMainPageSubPage _subPage = DebugMainPageSubPage.colors;
|
||||
@@ -52,6 +54,7 @@ class _DebugMainPageState extends State<DebugMainPage> {
|
||||
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.colors, label: Text('Theme')),
|
||||
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.requests, label: Text('Requests')),
|
||||
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.persistence, label: Text('Persistence')),
|
||||
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.logs, label: Text('Logs')),
|
||||
],
|
||||
selected: <DebugMainPageSubPage>{_subPage},
|
||||
onSelectionChanged: (Set<DebugMainPageSubPage> v) {
|
||||
|
87
flutter/lib/pages/debug/debug_request_view.dart
Normal file
87
flutter/lib/pages/debug/debug_request_view.dart
Normal file
@@ -0,0 +1,87 @@
|
||||
import 'package:fl_toast/fl_toast.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:simplecloudnotifier/components/layout/scaffold.dart';
|
||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||
|
||||
class DebugRequestViewPage extends StatelessWidget {
|
||||
final SCNRequest request;
|
||||
|
||||
DebugRequestViewPage({required this.request});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SCNScaffold(
|
||||
title: 'Request',
|
||||
showSearch: false,
|
||||
showDebug: false,
|
||||
child: SingleChildScrollView(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
...buildRow(context, "Name", request.name),
|
||||
...buildRow(context, "Timestamp (Start)", request.timestampStart.toString()),
|
||||
...buildRow(context, "Timestamp (End)", request.timestampEnd.toString()),
|
||||
...buildRow(context, "Duration", request.timestampEnd.difference(request.timestampStart).toString()),
|
||||
Divider(),
|
||||
...buildRow(context, "Method", request.method),
|
||||
...buildRow(context, "URL", request.url),
|
||||
if (request.requestHeaders.isNotEmpty) ...buildRow(context, "Request->Headers", request.requestHeaders.entries.map((v) => '${v.key} = ${v.value}').join('\n')),
|
||||
if (request.requestBody != '') ...buildRow(context, "Request->Body", request.requestBody),
|
||||
Divider(),
|
||||
if (request.responseStatusCode != 0) ...buildRow(context, "Response->Statuscode", request.responseStatusCode.toString()),
|
||||
if (request.responseBody != '') ...buildRow(context, "Reponse->Body", request.responseBody),
|
||||
if (request.responseHeaders.isNotEmpty) ...buildRow(context, "Reponse->Headers", request.responseHeaders.entries.map((v) => '${v.key} = ${v.value}').join('\n')),
|
||||
Divider(),
|
||||
if (request.error != '') ...buildRow(context, "Error", request.error),
|
||||
if (request.stackTrace != '') ...buildRow(context, "Stacktrace", request.stackTrace),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
List<Widget> buildRow(BuildContext context, String title, String value) {
|
||||
return [
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 0, horizontal: 8.0),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: Text(title, style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
IconButton(
|
||||
icon: FaIcon(
|
||||
FontAwesomeIcons.copy,
|
||||
),
|
||||
iconSize: 14,
|
||||
padding: EdgeInsets.fromLTRB(0, 0, 4, 0),
|
||||
constraints: BoxConstraints(),
|
||||
onPressed: () {
|
||||
Clipboard.setData(new ClipboardData(text: value));
|
||||
showPlatformToast(child: Text('Copied to clipboard'), context: ToastProvider.context);
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
Card.filled(
|
||||
shape: BeveledRectangleBorder(borderRadius: BorderRadius.circular(0)),
|
||||
color: request.type == 'SUCCESS' ? null : Theme.of(context).colorScheme.errorContainer,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2.0, horizontal: 6.0),
|
||||
child: SelectableText(
|
||||
value,
|
||||
minLines: 1,
|
||||
maxLines: 10,
|
||||
),
|
||||
),
|
||||
),
|
||||
];
|
||||
}
|
||||
}
|
@@ -1,4 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:simplecloudnotifier/pages/debug/debug_request_view.dart';
|
||||
import 'package:simplecloudnotifier/state/request_log.dart';
|
||||
|
||||
class DebugRequestsPage extends StatefulWidget {
|
||||
@override
|
||||
@@ -6,8 +10,82 @@ class DebugRequestsPage extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DebugRequestsPageState extends State<DebugRequestsPage> {
|
||||
Box<SCNRequest> requestsBox = Hive.box<SCNRequest>('scn-requests');
|
||||
|
||||
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Container(/* Add your UI components here */);
|
||||
return Container(
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: requestsBox.listenable(),
|
||||
builder: (context, Box<SCNRequest> box, _) {
|
||||
return ListView.builder(
|
||||
itemCount: requestsBox.length,
|
||||
itemBuilder: (context, listIndex) {
|
||||
final req = requestsBox.getAt(requestsBox.length - listIndex - 1)!;
|
||||
if (req.type == 'SUCCESS') {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 2.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => DebugRequestViewPage(request: req))),
|
||||
child: ListTile(
|
||||
title: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(_dateFormat.format(req.timestampStart), style: TextStyle(fontSize: 12)),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(req.name, style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
SizedBox(width: 2),
|
||||
Text('${req.timestampEnd.difference(req.timestampStart).inMilliseconds}ms', style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
subtitle: Text(req.type),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 2.0),
|
||||
child: GestureDetector(
|
||||
onTap: () => Navigator.push(context, MaterialPageRoute(builder: (context) => DebugRequestViewPage(request: req))),
|
||||
child: ListTile(
|
||||
tileColor: Theme.of(context).colorScheme.errorContainer,
|
||||
textColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||
title: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 120,
|
||||
child: Text(_dateFormat.format(req.timestampStart), style: TextStyle(fontSize: 12)),
|
||||
),
|
||||
Expanded(
|
||||
child: Text(req.name, style: TextStyle(fontWeight: FontWeight.bold)),
|
||||
),
|
||||
SizedBox(width: 2),
|
||||
Text('${req.timestampEnd.difference(req.timestampStart).inMilliseconds}ms', style: TextStyle(fontSize: 12)),
|
||||
],
|
||||
),
|
||||
subtitle: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(req.type),
|
||||
Text(
|
||||
req.error,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
)),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@@ -1 +1,29 @@
|
||||
import 'package:hive_flutter/hive_flutter.dart';
|
||||
|
||||
part 'application_log.g.dart';
|
||||
|
||||
class ApplicationLog {}
|
||||
|
||||
enum SCNLogLevel { debug, info, warning, error, fatal }
|
||||
|
||||
@HiveType(typeId: 101)
|
||||
class SCNLog extends HiveObject {
|
||||
@HiveField(0)
|
||||
final DateTime timestamp;
|
||||
@HiveField(1)
|
||||
final SCNLogLevel level;
|
||||
@HiveField(2)
|
||||
final String message;
|
||||
@HiveField(3)
|
||||
final String additional;
|
||||
@HiveField(4)
|
||||
final String trace;
|
||||
|
||||
SCNLog(
|
||||
this.timestamp,
|
||||
this.level,
|
||||
this.message,
|
||||
this.additional,
|
||||
this.trace,
|
||||
);
|
||||
}
|
||||
|
53
flutter/lib/state/application_log.g.dart
Normal file
53
flutter/lib/state/application_log.g.dart
Normal file
@@ -0,0 +1,53 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'application_log.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SCNLogAdapter extends TypeAdapter<SCNLog> {
|
||||
@override
|
||||
final int typeId = 101;
|
||||
|
||||
@override
|
||||
SCNLog read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return SCNLog(
|
||||
fields[0] as DateTime,
|
||||
fields[1] as SCNLogLevel,
|
||||
fields[2] as String,
|
||||
fields[3] as String,
|
||||
fields[4] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, SCNLog obj) {
|
||||
writer
|
||||
..writeByte(5)
|
||||
..writeByte(0)
|
||||
..write(obj.timestamp)
|
||||
..writeByte(1)
|
||||
..write(obj.level)
|
||||
..writeByte(2)
|
||||
..write(obj.message)
|
||||
..writeByte(3)
|
||||
..write(obj.additional)
|
||||
..writeByte(4)
|
||||
..write(obj.trace);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SCNLogAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
@@ -1,46 +0,0 @@
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:sqflite_common_ffi/sqflite_ffi.dart';
|
||||
import 'dart:io';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
class SCNDatabase {
|
||||
static SCNDatabase? instance = null;
|
||||
|
||||
final Database _db;
|
||||
|
||||
SCNDatabase._(this._db) {}
|
||||
|
||||
static create() async {
|
||||
var docPath = await getApplicationDocumentsDirectory();
|
||||
var dbpath = path.join(docPath.absolute.path, 'scn.db');
|
||||
|
||||
if (Platform.isWindows || Platform.isLinux) {
|
||||
sqfliteFfiInit();
|
||||
}
|
||||
|
||||
var db = await databaseFactoryFfi.openDatabase(dbpath,
|
||||
options: OpenDatabaseOptions(
|
||||
version: 1,
|
||||
onCreate: (db, version) async {
|
||||
initDatabase(db);
|
||||
},
|
||||
onUpgrade: (db, oldVersion, newVersion) async {
|
||||
upgradeDatabase(db, oldVersion, newVersion);
|
||||
},
|
||||
));
|
||||
|
||||
return instance = SCNDatabase._(db);
|
||||
}
|
||||
|
||||
static void initDatabase(Database db) async {
|
||||
await db.execute('CREATE TABLE requests (id INTEGER PRIMARY KEY, timestamp DATETIME, name TEXT, url TEXT, response_code INTEGER, response TEXT, status TEXT)');
|
||||
|
||||
await db.execute('CREATE TABLE logs (id INTEGER PRIMARY KEY, timestamp DATETIME, level TEXT, text TEXT, additional TEXT)');
|
||||
|
||||
await db.execute('CREATE TABLE messages (message_id INTEGER PRIMARY KEY, receive_timestamp DATETIME, channel_id TEXT, timestamp TEXT, data JSON)');
|
||||
}
|
||||
|
||||
static void upgradeDatabase(Database db, int oldVersion, int newVersion) {
|
||||
// ...
|
||||
}
|
||||
}
|
31
flutter/lib/state/globals.dart
Normal file
31
flutter/lib/state/globals.dart
Normal file
@@ -0,0 +1,31 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
class Globals {
|
||||
static final Globals _singleton = Globals._internal();
|
||||
|
||||
factory Globals() {
|
||||
return _singleton;
|
||||
}
|
||||
|
||||
Globals._internal();
|
||||
|
||||
String appName = '';
|
||||
String packageName = '';
|
||||
String version = '';
|
||||
String buildNumber = '';
|
||||
String platform = '';
|
||||
String hostname = '';
|
||||
|
||||
init() async {
|
||||
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
||||
|
||||
this.appName = packageInfo.appName;
|
||||
this.packageName = packageInfo.packageName;
|
||||
this.version = packageInfo.version;
|
||||
this.buildNumber = packageInfo.buildNumber;
|
||||
this.platform = Platform.operatingSystem;
|
||||
this.hostname = Platform.localHostname;
|
||||
}
|
||||
}
|
@@ -1 +1,144 @@
|
||||
import 'package:hive/hive.dart';
|
||||
import 'package:simplecloudnotifier/models/api_error.dart';
|
||||
|
||||
part 'request_log.g.dart';
|
||||
|
||||
class RequestLog {
|
||||
static void addRequestException(String name, DateTime tStart, String method, Uri uri, String reqbody, Map<String, String> reqheaders, dynamic e, StackTrace trace) {
|
||||
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
||||
timestampStart: tStart,
|
||||
timestampEnd: DateTime.now(),
|
||||
name: name,
|
||||
method: method,
|
||||
url: uri.toString(),
|
||||
requestHeaders: reqheaders,
|
||||
requestBody: reqbody,
|
||||
responseStatusCode: 0,
|
||||
responseHeaders: {},
|
||||
responseBody: '',
|
||||
type: 'EXCEPTION',
|
||||
error: (e is Exception) ? e.toString() : '$e',
|
||||
stackTrace: trace.toString(),
|
||||
));
|
||||
}
|
||||
|
||||
static void addRequestAPIError(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders, APIError apierr) {
|
||||
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
||||
timestampStart: t0,
|
||||
timestampEnd: DateTime.now(),
|
||||
name: name,
|
||||
method: method,
|
||||
url: uri.toString(),
|
||||
requestHeaders: reqheaders,
|
||||
requestBody: reqbody,
|
||||
responseStatusCode: responseStatusCode,
|
||||
responseHeaders: responseHeaders,
|
||||
responseBody: responseBody,
|
||||
type: 'API_ERROR',
|
||||
error: apierr.message,
|
||||
stackTrace: '',
|
||||
));
|
||||
}
|
||||
|
||||
static void addRequestErrorStatuscode(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders) {
|
||||
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
||||
timestampStart: t0,
|
||||
timestampEnd: DateTime.now(),
|
||||
name: name,
|
||||
method: method,
|
||||
url: uri.toString(),
|
||||
requestHeaders: reqheaders,
|
||||
requestBody: reqbody,
|
||||
responseStatusCode: responseStatusCode,
|
||||
responseHeaders: responseHeaders,
|
||||
responseBody: responseBody,
|
||||
type: 'ERROR_STATUSCODE',
|
||||
error: 'API request failed with status code $responseStatusCode',
|
||||
stackTrace: '',
|
||||
));
|
||||
}
|
||||
|
||||
static void addRequestSuccess(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders) {
|
||||
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
||||
timestampStart: t0,
|
||||
timestampEnd: DateTime.now(),
|
||||
name: name,
|
||||
method: method,
|
||||
url: uri.toString(),
|
||||
requestHeaders: reqheaders,
|
||||
requestBody: reqbody,
|
||||
responseStatusCode: responseStatusCode,
|
||||
responseHeaders: responseHeaders,
|
||||
responseBody: responseBody,
|
||||
type: 'SUCCESS',
|
||||
error: '',
|
||||
stackTrace: '',
|
||||
));
|
||||
}
|
||||
|
||||
static void addRequestDecodeError(String name, DateTime t0, String method, Uri uri, String reqbody, Map<String, String> reqheaders, int responseStatusCode, String responseBody, Map<String, String> responseHeaders, Object exc, StackTrace trace) {
|
||||
Hive.box<SCNRequest>('scn-requests').add(SCNRequest(
|
||||
timestampStart: t0,
|
||||
timestampEnd: DateTime.now(),
|
||||
name: name,
|
||||
method: method,
|
||||
url: uri.toString(),
|
||||
requestHeaders: reqheaders,
|
||||
requestBody: reqbody,
|
||||
responseStatusCode: responseStatusCode,
|
||||
responseHeaders: responseHeaders,
|
||||
responseBody: responseBody,
|
||||
type: 'DECODE_ERROR',
|
||||
error: (exc is Exception) ? exc.toString() : '$exc',
|
||||
stackTrace: trace.toString(),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
@HiveType(typeId: 100)
|
||||
class SCNRequest extends HiveObject {
|
||||
@HiveField(0)
|
||||
final DateTime timestampStart;
|
||||
@HiveField(1)
|
||||
final DateTime timestampEnd;
|
||||
@HiveField(2)
|
||||
final String name;
|
||||
@HiveField(3)
|
||||
final String type;
|
||||
@HiveField(4)
|
||||
final String error;
|
||||
@HiveField(5)
|
||||
final String stackTrace;
|
||||
|
||||
@HiveField(6)
|
||||
final String method;
|
||||
@HiveField(7)
|
||||
final String url;
|
||||
@HiveField(8)
|
||||
final Map<String, String> requestHeaders;
|
||||
@HiveField(12)
|
||||
final String requestBody;
|
||||
|
||||
@HiveField(9)
|
||||
final int responseStatusCode;
|
||||
@HiveField(10)
|
||||
final Map<String, String> responseHeaders;
|
||||
@HiveField(11)
|
||||
final String responseBody;
|
||||
|
||||
SCNRequest({
|
||||
required this.timestampStart,
|
||||
required this.timestampEnd,
|
||||
required this.name,
|
||||
required this.method,
|
||||
required this.url,
|
||||
required this.requestHeaders,
|
||||
required this.requestBody,
|
||||
required this.responseStatusCode,
|
||||
required this.responseHeaders,
|
||||
required this.responseBody,
|
||||
required this.type,
|
||||
required this.error,
|
||||
required this.stackTrace,
|
||||
});
|
||||
}
|
||||
|
77
flutter/lib/state/request_log.g.dart
Normal file
77
flutter/lib/state/request_log.g.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'request_log.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// TypeAdapterGenerator
|
||||
// **************************************************************************
|
||||
|
||||
class SCNRequestAdapter extends TypeAdapter<SCNRequest> {
|
||||
@override
|
||||
final int typeId = 100;
|
||||
|
||||
@override
|
||||
SCNRequest read(BinaryReader reader) {
|
||||
final numOfFields = reader.readByte();
|
||||
final fields = <int, dynamic>{
|
||||
for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(),
|
||||
};
|
||||
return SCNRequest(
|
||||
timestampStart: fields[0] as DateTime,
|
||||
timestampEnd: fields[1] as DateTime,
|
||||
name: fields[2] as String,
|
||||
method: fields[6] as String,
|
||||
url: fields[7] as String,
|
||||
requestHeaders: (fields[8] as Map).cast<String, String>(),
|
||||
requestBody: fields[12] as String,
|
||||
responseStatusCode: fields[9] as int,
|
||||
responseHeaders: (fields[10] as Map).cast<String, String>(),
|
||||
responseBody: fields[11] as String,
|
||||
type: fields[3] as String,
|
||||
error: fields[4] as String,
|
||||
stackTrace: fields[5] as String,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(BinaryWriter writer, SCNRequest obj) {
|
||||
writer
|
||||
..writeByte(13)
|
||||
..writeByte(0)
|
||||
..write(obj.timestampStart)
|
||||
..writeByte(1)
|
||||
..write(obj.timestampEnd)
|
||||
..writeByte(2)
|
||||
..write(obj.name)
|
||||
..writeByte(3)
|
||||
..write(obj.type)
|
||||
..writeByte(4)
|
||||
..write(obj.error)
|
||||
..writeByte(5)
|
||||
..write(obj.stackTrace)
|
||||
..writeByte(6)
|
||||
..write(obj.method)
|
||||
..writeByte(7)
|
||||
..write(obj.url)
|
||||
..writeByte(8)
|
||||
..write(obj.requestHeaders)
|
||||
..writeByte(12)
|
||||
..write(obj.requestBody)
|
||||
..writeByte(9)
|
||||
..write(obj.responseStatusCode)
|
||||
..writeByte(10)
|
||||
..write(obj.responseHeaders)
|
||||
..writeByte(11)
|
||||
..write(obj.responseBody);
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => typeId.hashCode;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) =>
|
||||
identical(this, other) ||
|
||||
other is SCNRequestAdapter &&
|
||||
runtimeType == other.runtimeType &&
|
||||
typeId == other.typeId;
|
||||
}
|
@@ -71,7 +71,7 @@ class UserAccount extends ChangeNotifier {
|
||||
throw Exception('Not authenticated');
|
||||
}
|
||||
|
||||
final user = await APIClient.getUser(_auth!.userId, _auth!.token);
|
||||
final user = await APIClient.getUser(_auth!, _auth!.userId);
|
||||
|
||||
setUser(user);
|
||||
|
||||
|
Reference in New Issue
Block a user