Hive, requestlog, etc

This commit is contained in:
Mike Schwörer 2024-05-25 18:09:39 +02:00
parent 227d7871c2
commit 7e347a70c2
Signed by: Mikescher
GPG Key ID: D3C7172E0A70F8CF
23 changed files with 1149 additions and 355 deletions

10
flutter/Makefile Normal file
View File

@ -0,0 +1,10 @@
run:
dart run build_runner build
flutter run
gen:
dart run build_runner build

View File

@ -11,7 +11,7 @@
- https://pub.dev/packages/sqflite - https://pub.dev/packages/sqflite
- https://pub.dev/packages/sqflite_common_ffi - https://pub.dev/packages/sqflite_common_ffi
- https://pub.dev/packages/hive

View File

@ -1,8 +1,13 @@
import 'dart:convert'; import 'dart:convert';
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http; 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/key_token_auth.dart';
import 'package:simplecloudnotifier/models/user.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/channel.dart';
import '../models/message.dart'; import '../models/message.dart';
@ -21,69 +26,141 @@ enum ChannelSelector {
class APIClient { class APIClient {
static const String _base = 'https://simplecloudnotifier.de/api/v2'; static const String _base = 'https://simplecloudnotifier.de/api/v2';
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();
final uri = Uri.parse('$_base/$relURL').replace(queryParameters: query ?? {});
final req = http.Request(method, uri);
if (jsonBody != null) {
req.body = jsonEncode(jsonBody);
req.headers['Content-Type'] = 'application/json';
}
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 { static Future<bool> verifyToken(String uid, String tok) async {
final uri = Uri.parse('$_base/users/$uid'); try {
final response = await http.get(uri, headers: {'Authorization': 'SCN $tok'}); await _request<void>(
name: 'verifyToken',
return (response.statusCode == 200); method: 'GET',
relURL: '/users/$uid',
fn: null,
auth: KeyTokenAuth(userId: uid, token: tok),
);
return true;
} catch (e) {
return false;
}
} }
static Future<User> getUser(String uid, String tok) async { static Future<User> getUser(KeyTokenAuth auth, String uid) async {
final uri = Uri.parse('$_base/users/$uid'); return await _request(
final response = await http.get(uri, headers: {'Authorization': 'SCN $tok'}); name: 'getUser',
method: 'GET',
if (response.statusCode != 200) { relURL: 'users/$uid',
throw Exception('API request failed'); fn: User.fromJson,
} auth: auth,
);
return User.fromJson(jsonDecode(response.body));
} }
static Future<List<ChannelWithSubscription>> getChannelList(KeyTokenAuth auth, ChannelSelector sel) async { static Future<List<ChannelWithSubscription>> getChannelList(KeyTokenAuth auth, ChannelSelector sel) async {
var url = '$_base/users/${auth.userId}/channels?selector=${sel.apiKey}'; return await _request(
final uri = Uri.parse(url); name: 'getChannelList',
method: 'GET',
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'}); relURL: 'users/${auth.userId}/channels',
query: {'selector': sel.apiKey},
if (response.statusCode != 200) { fn: (json) => ChannelWithSubscription.fromJsonArray(json['channels']),
throw Exception('API request failed'); auth: auth,
} );
final data = jsonDecode(response.body);
return data['channels'].map<ChannelWithSubscription>((e) => ChannelWithSubscription.fromJson(e)).toList() as List<ChannelWithSubscription>;
} }
static Future<(String, List<Message>)> getMessageList(KeyTokenAuth auth, String pageToken, int? pageSize) async { static Future<(String, List<Message>)> getMessageList(KeyTokenAuth auth, String pageToken, int? pageSize) async {
var url = '$_base/messages?next_page_token=$pageToken'; return await _request(
if (pageSize != null) { name: 'getMessageList',
url += '&page_size=$pageSize'; method: 'GET',
} relURL: 'messages',
final uri = Uri.parse(url); query: {'next_page_token': pageToken, if (pageSize != null) 'page_size': pageSize.toString()},
fn: (json) => Message.fromPaginatedJsonArray(json, 'messages', 'next_page_token'),
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'}); auth: auth,
);
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);
} }
static Future<Message> getMessage(KeyTokenAuth auth, String msgid) async { static Future<Message> getMessage(KeyTokenAuth auth, String msgid) async {
final uri = Uri.parse('$_base/messages/$msgid'); return await _request(
final response = await http.get(uri, headers: {'Authorization': 'SCN ${auth.token}'}); name: 'getMessage',
method: 'GET',
if (response.statusCode != 200) { relURL: 'messages/$msgid',
throw Exception('API request failed'); query: {},
} fn: Message.fromJson,
auth: auth,
return Message.fromJson(jsonDecode(response.body)); );
} }
} }

View File

@ -1,13 +1,34 @@
import 'package:fl_toast/fl_toast.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.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/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/globals.dart';
import 'package:simplecloudnotifier/state/request_log.dart';
import 'package:simplecloudnotifier/state/user_account.dart'; import 'package:simplecloudnotifier/state/user_account.dart';
void main() async { 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( runApp(
MultiProvider( MultiProvider(
@ -31,7 +52,7 @@ class SCNApp extends StatelessWidget {
@override @override
Widget build(BuildContext context) { 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>( return Consumer<AppTheme>(
builder: (context, appTheme, child) => MaterialApp( 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), colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue, brightness: appTheme.darkMode ? Brightness.dark : Brightness.light),
useMaterial3: true, useMaterial3: true,
), ),
home: const SCNNavLayout(), home: const ToastProvider(child: SCNNavLayout()),
), ),
); );
} }

View 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'],
);
}
}

View File

@ -24,31 +24,17 @@ class Channel {
}); });
factory Channel.fromJson(Map<String, dynamic> json) { factory Channel.fromJson(Map<String, dynamic> json) {
return switch (json) { return Channel(
{ channelID: json['channel_id'],
'channel_id': String channelID, ownerUserID: json['owner_user_id'],
'owner_user_id': String ownerUserID, internalName: json['internal_name'],
'internal_name': String internalName, displayName: json['display_name'],
'display_name': String displayName, descriptionName: json['description_name'],
'description_name': String? descriptionName, subscribeKey: json['subscribe_key'],
'subscribe_key': String? subscribeKey, timestampCreated: json['timestamp_created'],
'timestamp_created': String timestampCreated, timestampLastSent: json['timestamp_lastsent'],
'timestamp_lastsent': String? timestampLastSent, messagesSent: json['messages_sent'],
'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.'),
};
} }
} }
@ -69,32 +55,21 @@ class ChannelWithSubscription extends Channel {
}); });
factory ChannelWithSubscription.fromJson(Map<String, dynamic> json) { factory ChannelWithSubscription.fromJson(Map<String, dynamic> json) {
return switch (json) { return ChannelWithSubscription(
{ channelID: json['channel_id'],
'channel_id': String channelID, ownerUserID: json['owner_user_id'],
'owner_user_id': String ownerUserID, internalName: json['internal_name'],
'internal_name': String internalName, displayName: json['display_name'],
'display_name': String displayName, descriptionName: json['description_name'],
'description_name': String? descriptionName, subscribeKey: json['subscribe_key'],
'subscribe_key': String? subscribeKey, timestampCreated: json['timestamp_created'],
'timestamp_created': String timestampCreated, timestampLastSent: json['timestamp_lastsent'],
'timestamp_lastsent': String? timestampLastSent, messagesSent: json['messages_sent'],
'messages_sent': int messagesSent, subscription: Subscription.fromJson(json['subscription']),
'subscription': dynamic subscription, );
} => }
ChannelWithSubscription(
channelID: channelID, static List<ChannelWithSubscription> fromJsonArray(List<dynamic> jsonArr) {
ownerUserID: ownerUserID, return jsonArr.map<ChannelWithSubscription>((e) => ChannelWithSubscription.fromJson(e)).toList();
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.'),
};
} }
} }

View File

@ -30,38 +30,28 @@ class Message {
}); });
factory Message.fromJson(Map<String, dynamic> json) { factory Message.fromJson(Map<String, dynamic> json) {
return switch (json) { return Message(
{ messageID: json['message_id'],
'message_id': String messageID, senderUserID: json['sender_user_id'],
'sender_user_id': String senderUserID, channelInternalName: json['channel_internal_name'],
'channel_internal_name': String channelInternalName, channelID: json['channel_id'],
'channel_id': String channelID, senderName: json['sender_name'],
'sender_name': String? senderName, senderIP: json['sender_ip'],
'sender_ip': String senderIP, timestamp: json['timestamp'],
'timestamp': String timestamp, title: json['title'],
'title': String title, content: json['content'],
'content': String? content, priority: json['priority'],
'priority': int priority, userMessageID: json['usr_message_id'],
'usr_message_id': String? userMessageID, usedKeyID: json['used_key_id'],
'used_key_id': String usedKeyID, trimmed: json['trimmed'],
'trimmed': bool trimmed, );
} => }
Message(
messageID: messageID, static fromPaginatedJsonArray(Map<String, dynamic> data, String keyMessages, String keyToken) {
senderUserID: senderUserID, final npt = data[keyToken] as String;
channelInternalName: channelInternalName,
channelID: channelID, final messages = (data[keyMessages] as List<dynamic>).map<Message>((e) => Message.fromJson(e)).toList();
senderName: senderName,
senderIP: senderIP, return (npt, messages);
timestamp: timestamp,
title: title,
content: content,
priority: priority,
userMessageID: userMessageID,
usedKeyID: usedKeyID,
trimmed: trimmed,
),
_ => throw const FormatException('Failed to decode Message.'),
};
} }
} }

View File

@ -18,26 +18,14 @@ class Subscription {
}); });
factory Subscription.fromJson(Map<String, dynamic> json) { factory Subscription.fromJson(Map<String, dynamic> json) {
return switch (json) { return Subscription(
{ subscriptionID: json['subscription_id'],
'subscription_id': String subscriptionID, subscriberUserID: json['subscriber_user_id'],
'subscriber_user_id': String subscriberUserID, channelOwnerUserID: json['channel_owner_user_id'],
'channel_owner_user_id': String channelOwnerUserID, channelID: json['channel_id'],
'channel_id': String channelID, channelInternalName: json['channel_internal_name'],
'channel_internal_name': String channelInternalName, timestampCreated: json['timestamp_created'],
'timestamp_created': String timestampCreated, confirmed: json['confirmed'],
'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.'),
};
} }
} }

View File

@ -40,48 +40,25 @@ class User {
}); });
factory User.fromJson(Map<String, dynamic> json) { factory User.fromJson(Map<String, dynamic> json) {
return switch (json) { return User(
{ userID: json['user_id'],
'user_id': String userID, username: json['username'],
'username': String? username, timestampCreated: json['timestamp_created'],
'timestamp_created': String timestampCreated, timestampLastRead: json['timestamp_lastread'],
'timestamp_lastread': String? timestampLastRead, timestampLastSent: json['timestamp_lastsent'],
'timestamp_lastsent': String? timestampLastSent, messagesSent: json['messages_sent'],
'messages_sent': int messagesSent, quotaUsed: json['quota_used'],
'quota_used': int quotaUsed, quotaRemaining: json['quota_remaining'],
'quota_remaining': int quotaRemaining, quotaPerDay: json['quota_max'],
'quota_max': int quotaPerDay, isPro: json['is_pro'],
'is_pro': bool isPro, defaultChannel: json['default_channel'],
'default_channel': String defaultChannel, maxBodySize: json['max_body_size'],
'max_body_size': int maxBodySize, maxTitleLength: json['max_title_length'],
'max_title_length': int maxTitleLength, defaultPriority: json['default_priority'],
'default_priority': int defaultPriority, maxChannelNameLength: json['max_channel_name_length'],
'max_channel_name_length': int maxChannelNameLength, maxChannelDescriptionLength: json['max_channel_description_length'],
'max_channel_description_length': int maxChannelDescriptionLength, maxSenderNameLength: json['max_sender_name_length'],
'max_sender_name_length': int maxSenderNameLength, maxUserMessageIDLength: json['max_user_message_id_length'],
'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.'),
};
} }
} }

View 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 */);
}
}

View File

@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:simplecloudnotifier/components/layout/scaffold.dart'; import 'package:simplecloudnotifier/components/layout/scaffold.dart';
import 'package:simplecloudnotifier/pages/debug/debug_colors.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_persistence.dart';
import 'package:simplecloudnotifier/pages/debug/debug_requests.dart'; import 'package:simplecloudnotifier/pages/debug/debug_requests.dart';
@ -9,13 +10,14 @@ class DebugMainPage extends StatefulWidget {
_DebugMainPageState createState() => _DebugMainPageState(); _DebugMainPageState createState() => _DebugMainPageState();
} }
enum DebugMainPageSubPage { colors, requests, persistence } enum DebugMainPageSubPage { colors, requests, persistence, logs }
class _DebugMainPageState extends State<DebugMainPage> { class _DebugMainPageState extends State<DebugMainPage> {
final Map<DebugMainPageSubPage, Widget> _subpages = { final Map<DebugMainPageSubPage, Widget> _subpages = {
DebugMainPageSubPage.colors: DebugColorsPage(), DebugMainPageSubPage.colors: DebugColorsPage(),
DebugMainPageSubPage.requests: DebugRequestsPage(), DebugMainPageSubPage.requests: DebugRequestsPage(),
DebugMainPageSubPage.persistence: DebugPersistencePage(), DebugMainPageSubPage.persistence: DebugPersistencePage(),
DebugMainPageSubPage.logs: DebugLogsPage(),
}; };
DebugMainPageSubPage _subPage = DebugMainPageSubPage.colors; 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.colors, label: Text('Theme')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.requests, label: Text('Requests')), ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.requests, label: Text('Requests')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.persistence, label: Text('Persistence')), ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.persistence, label: Text('Persistence')),
ButtonSegment<DebugMainPageSubPage>(value: DebugMainPageSubPage.logs, label: Text('Logs')),
], ],
selected: <DebugMainPageSubPage>{_subPage}, selected: <DebugMainPageSubPage>{_subPage},
onSelectionChanged: (Set<DebugMainPageSubPage> v) { onSelectionChanged: (Set<DebugMainPageSubPage> v) {

View 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,
),
),
),
];
}
}

View File

@ -1,4 +1,8 @@
import 'package:flutter/material.dart'; 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 { class DebugRequestsPage extends StatefulWidget {
@override @override
@ -6,8 +10,82 @@ class DebugRequestsPage extends StatefulWidget {
} }
class _DebugRequestsPageState extends State<DebugRequestsPage> { class _DebugRequestsPageState extends State<DebugRequestsPage> {
Box<SCNRequest> requestsBox = Hive.box<SCNRequest>('scn-requests');
static final _dateFormat = DateFormat('yyyy-MM-dd kk:mm');
@override @override
Widget build(BuildContext context) { 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,
),
],
)),
),
);
}
},
);
},
),
);
} }
} }

View File

@ -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,
);
}

View 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;
}

View File

@ -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) {
// ...
}
}

View 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;
}
}

View File

@ -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,
});
}

View 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;
}

View File

@ -71,7 +71,7 @@ class UserAccount extends ChangeNotifier {
throw Exception('Not authenticated'); throw Exception('Not authenticated');
} }
final user = await APIClient.getUser(_auth!.userId, _auth!.token); final user = await APIClient.getUser(_auth!, _auth!.userId);
setUser(user); setUser(user);

View File

@ -5,11 +5,13 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import package_info_plus
import path_provider_foundation import path_provider_foundation
import shared_preferences_foundation import shared_preferences_foundation
import url_launcher_macos import url_launcher_macos
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))

View File

@ -1,6 +1,30 @@
# Generated by pub # Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile # See https://dart.dev/tools/pub/glossary#lockfile
packages: packages:
_fe_analyzer_shared:
dependency: transitive
description:
name: _fe_analyzer_shared
sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7"
url: "https://pub.dev"
source: hosted
version: "67.0.0"
analyzer:
dependency: transitive
description:
name: analyzer
sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d"
url: "https://pub.dev"
source: hosted
version: "6.4.1"
args:
dependency: transitive
description:
name: args
sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
url: "https://pub.dev"
source: hosted
version: "2.5.0"
async: async:
dependency: transitive dependency: transitive
description: description:
@ -17,6 +41,70 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
build:
dependency: transitive
description:
name: build
sha256: "80184af8b6cb3e5c1c4ec6d8544d27711700bc3e6d2efad04238c7b5290889f0"
url: "https://pub.dev"
source: hosted
version: "2.4.1"
build_config:
dependency: transitive
description:
name: build_config
sha256: bf80fcfb46a29945b423bd9aad884590fb1dc69b330a4d4700cac476af1708d1
url: "https://pub.dev"
source: hosted
version: "1.1.1"
build_daemon:
dependency: transitive
description:
name: build_daemon
sha256: "0343061a33da9c5810b2d6cee51945127d8f4c060b7fbdd9d54917f0a3feaaa1"
url: "https://pub.dev"
source: hosted
version: "4.0.1"
build_resolvers:
dependency: transitive
description:
name: build_resolvers
sha256: "339086358431fa15d7eca8b6a36e5d783728cf025e559b834f4609a1fcfb7b0a"
url: "https://pub.dev"
source: hosted
version: "2.4.2"
build_runner:
dependency: "direct dev"
description:
name: build_runner
sha256: "3ac61a79bfb6f6cc11f693591063a7f19a7af628dc52f141743edac5c16e8c22"
url: "https://pub.dev"
source: hosted
version: "2.4.9"
build_runner_core:
dependency: transitive
description:
name: build_runner_core
sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799"
url: "https://pub.dev"
source: hosted
version: "7.3.0"
built_collection:
dependency: transitive
description:
name: built_collection
sha256: "376e3dd27b51ea877c28d525560790aee2e6fbb5f20e2f85d5081027d94e2100"
url: "https://pub.dev"
source: hosted
version: "5.1.1"
built_value:
dependency: transitive
description:
name: built_value
sha256: c7913a9737ee4007efedaffc968c049fd0f3d0e49109e778edc10de9426005cb
url: "https://pub.dev"
source: hosted
version: "8.9.2"
characters: characters:
dependency: transitive dependency: transitive
description: description:
@ -25,6 +113,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.3.0" version: "1.3.0"
checked_yaml:
dependency: transitive
description:
name: checked_yaml
sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
url: "https://pub.dev"
source: hosted
version: "2.0.3"
clock: clock:
dependency: transitive dependency: transitive
description: description:
@ -33,6 +129,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.1.1" version: "1.1.1"
code_builder:
dependency: transitive
description:
name: code_builder
sha256: f692079e25e7869c14132d39f223f8eec9830eb76131925143b2129c4bb01b37
url: "https://pub.dev"
source: hosted
version: "4.10.0"
collection: collection:
dependency: transitive dependency: transitive
description: description:
@ -41,6 +145,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.18.0" version: "1.18.0"
convert:
dependency: transitive
description:
name: convert
sha256: "0f08b14755d163f6e2134cb58222dd25ea2a2ee8a195e53983d57c075324d592"
url: "https://pub.dev"
source: hosted
version: "3.1.1"
crypto:
dependency: transitive
description:
name: crypto
sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
url: "https://pub.dev"
source: hosted
version: "3.0.3"
cupertino_icons: cupertino_icons:
dependency: "direct main" dependency: "direct main"
description: description:
@ -49,6 +169,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.8" version: "1.0.8"
dart_style:
dependency: transitive
description:
name: dart_style
sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9"
url: "https://pub.dev"
source: hosted
version: "2.3.6"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -73,6 +201,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
fixnum:
dependency: transitive
description:
name: fixnum
sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
fl_toast:
dependency: "direct main"
description:
name: fl_toast
sha256: "0f7bbce90d1b75463a414c6a5476e45bd93fa7c4adccce1690076f4d1ef77c42"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
flutter: flutter:
dependency: "direct main" dependency: "direct main"
description: flutter description: flutter
@ -111,6 +255,54 @@ packages:
relative: true relative: true
source: path source: path
version: "10.7.0" version: "10.7.0"
frontend_server_client:
dependency: transitive
description:
name: frontend_server_client
sha256: f64a0333a82f30b0cca061bc3d143813a486dc086b574bfb233b7c1372427694
url: "https://pub.dev"
source: hosted
version: "4.0.0"
glob:
dependency: transitive
description:
name: glob
sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63"
url: "https://pub.dev"
source: hosted
version: "2.1.2"
graphs:
dependency: transitive
description:
name: graphs
sha256: aedc5a15e78fc65a6e23bcd927f24c64dd995062bcd1ca6eda65a3cff92a4d19
url: "https://pub.dev"
source: hosted
version: "2.3.1"
hive:
dependency: "direct main"
description:
name: hive
sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941"
url: "https://pub.dev"
source: hosted
version: "2.2.3"
hive_flutter:
dependency: "direct main"
description:
name: hive_flutter
sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc
url: "https://pub.dev"
source: hosted
version: "1.1.0"
hive_generator:
dependency: "direct dev"
description:
name: hive_generator
sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4"
url: "https://pub.dev"
source: hosted
version: "2.0.1"
http: http:
dependency: "direct main" dependency: "direct main"
description: description:
@ -119,6 +311,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.1" version: "1.2.1"
http_multi_server:
dependency: transitive
description:
name: http_multi_server
sha256: "97486f20f9c2f7be8f514851703d0119c3596d14ea63227af6f7a481ef2b2f8b"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
http_parser: http_parser:
dependency: transitive dependency: transitive
description: description:
@ -143,6 +343,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.19.0" version: "0.19.0"
io:
dependency: transitive
description:
name: io
sha256: "2ec25704aba361659e10e3e5f5d672068d332fc8ac516421d483a11e5cbd061e"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
js:
dependency: transitive
description:
name: js
sha256: c1b2e9b5ea78c45e1a0788d29606ba27dc5f71f019f32ca5140f61ef071838cf
url: "https://pub.dev"
source: hosted
version: "0.7.1"
json_annotation:
dependency: transitive
description:
name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
url: "https://pub.dev"
source: hosted
version: "4.9.0"
leak_tracker: leak_tracker:
dependency: transitive dependency: transitive
description: description:
@ -175,6 +399,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.0.0" version: "4.0.0"
logging:
dependency: transitive
description:
name: logging
sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
url: "https://pub.dev"
source: hosted
version: "1.2.0"
matcher: matcher:
dependency: transitive dependency: transitive
description: description:
@ -199,6 +431,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.11.0" version: "1.11.0"
mime:
dependency: transitive
description:
name: mime
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
url: "https://pub.dev"
source: hosted
version: "1.0.5"
nested: nested:
dependency: transitive dependency: transitive
description: description:
@ -207,6 +447,30 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.0" version: "1.0.0"
package_config:
dependency: transitive
description:
name: package_config
sha256: "1c5b77ccc91e4823a5af61ee74e6b972db1ef98c2ff5a18d3161c982a55448bd"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
package_info_plus:
dependency: "direct main"
description:
name: package_info_plus
sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
url: "https://pub.dev"
source: hosted
version: "8.0.0"
package_info_plus_platform_interface:
dependency: transitive
description:
name: package_info_plus_platform_interface
sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
url: "https://pub.dev"
source: hosted
version: "3.0.0"
path: path:
dependency: transitive dependency: transitive
description: description:
@ -279,6 +543,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.8" version: "2.1.8"
pool:
dependency: transitive
description:
name: pool
sha256: "20fe868b6314b322ea036ba325e6fc0711a22948856475e2c2b6306e8ab39c2a"
url: "https://pub.dev"
source: hosted
version: "1.5.1"
provider: provider:
dependency: "direct main" dependency: "direct main"
description: description:
@ -287,6 +559,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "6.1.2" version: "6.1.2"
pub_semver:
dependency: transitive
description:
name: pub_semver
sha256: "40d3ab1bbd474c4c2328c91e3a7df8c6dd629b79ece4c4bd04bee496a224fb0c"
url: "https://pub.dev"
source: hosted
version: "2.1.4"
pubspec_parse:
dependency: transitive
description:
name: pubspec_parse
sha256: c63b2876e58e194e4b0828fcb080ad0e06d051cb607a6be51a9e084f47cb9367
url: "https://pub.dev"
source: hosted
version: "1.2.3"
qr: qr:
dependency: transitive dependency: transitive
description: description:
@ -359,6 +647,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.3.2" version: "2.3.2"
shelf:
dependency: transitive
description:
name: shelf
sha256: ad29c505aee705f41a4d8963641f91ac4cee3c8fad5947e033390a7bd8180fa4
url: "https://pub.dev"
source: hosted
version: "1.4.1"
shelf_web_socket:
dependency: transitive
description:
name: shelf_web_socket
sha256: "9ca081be41c60190ebcb4766b2486a7d50261db7bd0f5d9615f2d653637a84c1"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
sky_engine: sky_engine:
dependency: transitive dependency: transitive
description: flutter description: flutter
@ -372,6 +676,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.2.12" version: "0.2.12"
source_gen:
dependency: transitive
description:
name: source_gen
sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832"
url: "https://pub.dev"
source: hosted
version: "1.5.0"
source_helper:
dependency: transitive
description:
name: source_helper
sha256: "6adebc0006c37dd63fe05bca0a929b99f06402fc95aa35bf36d67f5c06de01fd"
url: "https://pub.dev"
source: hosted
version: "1.3.4"
source_span: source_span:
dependency: transitive dependency: transitive
description: description:
@ -380,30 +700,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.10.0" version: "1.10.0"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4"
url: "https://pub.dev"
source: hosted
version: "2.5.4"
sqflite_common_ffi:
dependency: "direct main"
description:
name: sqflite_common_ffi
sha256: "4d6137c29e930d6e4a8ff373989dd9de7bac12e3bc87bce950f6e844e8ad3bb5"
url: "https://pub.dev"
source: hosted
version: "2.3.3"
sqlite3:
dependency: transitive
description:
name: sqlite3
sha256: b384f598b813b347c5a7e5ffad82cbaff1bec3d1561af267041e66f6f0899295
url: "https://pub.dev"
source: hosted
version: "2.4.3"
stack_trace: stack_trace:
dependency: transitive dependency: transitive
description: description:
@ -420,6 +716,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.2" version: "2.1.2"
stream_transform:
dependency: transitive
description:
name: stream_transform
sha256: "14a00e794c7c11aa145a170587321aedce29769c08d7f58b1d141da75e3b1c6f"
url: "https://pub.dev"
source: hosted
version: "2.1.0"
string_scanner: string_scanner:
dependency: transitive dependency: transitive
description: description:
@ -428,14 +732,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
synchronized:
dependency: transitive
description:
name: synchronized
sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558"
url: "https://pub.dev"
source: hosted
version: "3.1.0+1"
term_glyph: term_glyph:
dependency: transitive dependency: transitive
description: description:
@ -452,6 +748,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.6.1" version: "0.6.1"
timing:
dependency: transitive
description:
name: timing
sha256: "70a3b636575d4163c477e6de42f247a23b315ae20e86442bebe32d3cabf61c32"
url: "https://pub.dev"
source: hosted
version: "1.0.1"
typed_data: typed_data:
dependency: transitive dependency: transitive
description: description:
@ -540,6 +844,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "13.0.0"
watcher:
dependency: transitive
description:
name: watcher
sha256: "3d2ad6751b3c16cf07c7fca317a1413b3f26530319181b37e3b9039b84fc01d8"
url: "https://pub.dev"
source: hosted
version: "1.1.0"
web: web:
dependency: transitive dependency: transitive
description: description:
@ -548,6 +860,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.5.1" version: "0.5.1"
web_socket_channel:
dependency: transitive
description:
name: web_socket_channel
sha256: "58c6666b342a38816b2e7e50ed0f1e261959630becd4c879c4f26bfa14aa5a42"
url: "https://pub.dev"
source: hosted
version: "2.4.5"
win32: win32:
dependency: transitive dependency: transitive
description: description:
@ -564,6 +884,14 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.4" version: "1.0.4"
yaml:
dependency: transitive
description:
name: yaml
sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
url: "https://pub.dev"
source: hosted
version: "3.1.2"
sdks: sdks:
dart: ">=3.3.0 <4.0.0" dart: ">=3.3.0 <4.0.0"
flutter: ">=3.19.0" flutter: ">=3.19.0"

View File

@ -1,40 +1,16 @@
name: simplecloudnotifier name: simplecloudnotifier
description: "A new Flutter project." description: "A new Flutter project."
# The following line prevents the package from being accidentally published to publish_to: 'none'
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 2.0.0+100 version: 2.0.0+100
environment: environment:
sdk: '>=3.2.6 <4.0.0' sdk: '>=3.2.6 <4.0.0'
# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
font_awesome_flutter: '>= 4.7.0' font_awesome_flutter: '>= 4.7.0'
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2 cupertino_icons: ^1.0.2
http: ^1.2.0 http: ^1.2.0
provider: ^6.1.1 provider: ^6.1.1
@ -43,8 +19,11 @@ dependencies:
url_launcher: ^6.2.4 url_launcher: ^6.2.4
infinite_scroll_pagination: ^4.0.0 infinite_scroll_pagination: ^4.0.0
intl: ^0.19.0 intl: ^0.19.0
sqflite_common_ffi: ^2.3.3
path_provider: ^2.1.3 path_provider: ^2.1.3
hive: ^2.2.3
hive_flutter: ^1.1.0
package_info_plus: ^8.0.0
fl_toast: ^3.2.0
dependency_overrides: dependency_overrides:
@ -56,51 +35,9 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# The "flutter_lints" package below contains a set of recommended lints to
# encourage good coding practices. The lint set provided by the package is
# activated in the `analysis_options.yaml` file located at the root of your
# package. See that file for information about deactivating specific lint
# rules and activating additional ones.
flutter_lints: ^4.0.0 flutter_lints: ^4.0.0
hive_generator: ^2.0.1
build_runner: ^2.1.4
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter packages.
flutter: flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages