diff --git a/lib/alarm/data/alarm_events_sort_options.dart b/lib/alarm/data/alarm_events_sort_options.dart new file mode 100644 index 00000000..fcbd93a6 --- /dev/null +++ b/lib/alarm/data/alarm_events_sort_options.dart @@ -0,0 +1,27 @@ +import 'package:clock_app/alarm/types/alarm.dart'; +import 'package:clock_app/alarm/types/alarm_event.dart'; +import 'package:clock_app/common/types/list_filter.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +final List> alarmEventSortOptions = [ + ListSortOption((context) => "Earlies start date", sortStartDateAscending), + ListSortOption((context) => "Latest start date", sortStartDateDescending), + ListSortOption((context) => "Earlies event date", sortEventDateAscending), + ListSortOption((context) => "Latest event date", sortEventDateDescending), +]; + +int sortStartDateAscending(AlarmEvent a, AlarmEvent b) { + return a.startDate.compareTo(b.startDate); +} + +int sortStartDateDescending(AlarmEvent a, AlarmEvent b) { + return b.startDate.compareTo(a.startDate); +} + +int sortEventDateAscending(AlarmEvent a, AlarmEvent b) { + return a.eventTime.compareTo(b.eventTime); +} + +int sortEventDateDescending(AlarmEvent a, AlarmEvent b) { + return b.eventTime.compareTo(a.eventTime); +} diff --git a/lib/alarm/logic/alarm_isolate.dart b/lib/alarm/logic/alarm_isolate.dart index 6239e5f6..8f297163 100644 --- a/lib/alarm/logic/alarm_isolate.dart +++ b/lib/alarm/logic/alarm_isolate.dart @@ -27,7 +27,7 @@ const String setAlarmVolumePortName = "setAlarmVolumePort"; @pragma('vm:entry-point') void triggerScheduledNotification(int scheduleId, Json params) async { FlutterError.onError = (FlutterErrorDetails details) { - logger.f(details.exception.toString()); + logger.f("Error in triggerScheduledNotification isolate: ${details.exception.toString()}"); }; logger.i( diff --git a/lib/alarm/screens/alarm_events_screen.dart b/lib/alarm/screens/alarm_events_screen.dart index 3222f224..1f2378e6 100644 --- a/lib/alarm/screens/alarm_events_screen.dart +++ b/lib/alarm/screens/alarm_events_screen.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'dart:typed_data'; import 'package:clock_app/alarm/data/alarm_events_list_filters.dart'; +import 'package:clock_app/alarm/data/alarm_events_sort_options.dart'; import 'package:clock_app/alarm/types/alarm_event.dart'; import 'package:clock_app/alarm/widgets/alarm_event_card.dart'; import 'package:clock_app/common/utils/json_serialize.dart'; @@ -73,6 +74,7 @@ class _AlarmEventsScreenState extends State { placeholderText: "No alarm events", reloadOnPop: true, listFilters: alarmEventsListFilters, + sortOptions: alarmEventSortOptions, ), ), ], diff --git a/lib/alarm/widgets/alarm_event_card.dart b/lib/alarm/widgets/alarm_event_card.dart index 64b6076d..864f4dcd 100644 --- a/lib/alarm/widgets/alarm_event_card.dart +++ b/lib/alarm/widgets/alarm_event_card.dart @@ -15,38 +15,35 @@ class AlarmEventCard extends StatelessWidget { Color textColor = colorScheme.onSurface.withOpacity(0.8); - return Column( - children: [ - Padding( - padding: const EdgeInsets.only( - left: 16.0, right: 16.0, top: 8.0, bottom: 8.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Text(event.isActive ? "Active" : "Inactive", - style: textTheme.labelMedium?.copyWith( - color: event.isActive - ? colorScheme.primary - : colorScheme.onSurface)), - Text('Scheduled for: ${event.startDate}', - style: textTheme.labelMedium?.copyWith(color: textColor)), - Text( - 'Type: ${event.notificationType == ScheduledNotificationType.alarm ? "Alarm" : "Timer"}', - style: textTheme.labelMedium?.copyWith(color: textColor)), - Text('Created at: ${event.eventTime}', - style: textTheme.labelMedium?.copyWith(color: textColor)), - Text( - 'Description: ${event.description}', - style: textTheme.labelMedium?.copyWith(color: textColor), - maxLines: 5, - ), - Text('Schedule Id: ${event.scheduleId}', - style: textTheme.labelMedium?.copyWith(color: textColor)), - ], + return Padding( + padding: const EdgeInsets.only( + left: 16.0, right: 16.0, top: 8.0, bottom: 8.0), + child: Column( + + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Text(event.isActive ? "Active" : "Inactive", + style: textTheme.labelMedium?.copyWith( + color: event.isActive + ? colorScheme.primary + : colorScheme.onSurface)), + Text('Scheduled for: ${event.startDate}', + style: textTheme.labelMedium?.copyWith(color: textColor)), + Text( + 'Type: ${event.notificationType == ScheduledNotificationType.alarm ? "Alarm" : "Timer"}', + style: textTheme.labelMedium?.copyWith(color: textColor)), + Text('Created at: ${event.eventTime}', + style: textTheme.labelMedium?.copyWith(color: textColor)), + Text( + 'Description: ${event.description}', + style: textTheme.labelMedium?.copyWith(color: textColor), + maxLines: 5, ), - ), - ], + Text('Schedule Id: ${event.scheduleId}', + style: textTheme.labelMedium?.copyWith(color: textColor)), + ], + ), ); } } diff --git a/lib/common/data/paths.dart b/lib/common/data/paths.dart index d670eb47..b03dd29a 100644 --- a/lib/common/data/paths.dart +++ b/lib/common/data/paths.dart @@ -45,3 +45,7 @@ Future getTimezonesDatabasePath() async { Future getLogsFilePath() async { return path.join(await getAppDataDirectoryPath(), "logs.txt"); } + +String getLogsFilePathSync(){ + return path.join(getAppDataDirectoryPathSync(), "logs.txt"); +} diff --git a/lib/common/utils/snackbar.dart b/lib/common/utils/snackbar.dart index 7a8e4708..81cb801a 100644 --- a/lib/common/utils/snackbar.dart +++ b/lib/common/utils/snackbar.dart @@ -6,13 +6,18 @@ void showSnackBar(BuildContext context, String text, ThemeData theme = Theme.of(context); ColorScheme colorScheme = theme.colorScheme; Color? color = error ? colorScheme.error : null; + Duration duration = + error ? const Duration(hours: 999) : const Duration(seconds: 4); ScaffoldMessenger.of(context).removeCurrentSnackBar(); - ScaffoldMessenger.of(context) - .showSnackBar(getSnackbar(text, fab: fab, navBar: navBar, color: color)); + ScaffoldMessenger.of(context).showSnackBar(getSnackbar(text, + fab: fab, navBar: navBar, color: color, duration: duration)); } SnackBar getSnackbar(String text, - {bool fab = false, bool navBar = false, Color? color}) { + {bool fab = false, + bool navBar = false, + Color? color, + Duration duration = const Duration(seconds: 4)}) { double left = 20; double right = 20; double bottom = 12; @@ -57,8 +62,9 @@ SnackBar getSnackbar(String text, right: right, bottom: bottom, ), - padding: EdgeInsets.zero, + padding: EdgeInsets.zero, elevation: 2, dismissDirection: DismissDirection.vertical, + duration: duration, ); } diff --git a/lib/common/widgets/list/custom_list_view.dart b/lib/common/widgets/list/custom_list_view.dart index 1f978950..52547c5f 100644 --- a/lib/common/widgets/list/custom_list_view.dart +++ b/lib/common/widgets/list/custom_list_view.dart @@ -267,8 +267,10 @@ class _CustomListViewState void _handleCustomAction(ListFilterCustomAction action) { final list = _getActionableItems(); - List items = list.where((item) => - widget.listFilters.every((filter) => filter.filterFunction(item))).toList(); + List items = list + .where((item) => + widget.listFilters.every((filter) => filter.filterFunction(item))) + .toList(); action.action(items); _endSelection(); @@ -350,18 +352,20 @@ class _CustomListViewState crossAxisAlignment: CrossAxisAlignment.start, children: [ ListFilterBar( - listFilters: widget.listFilters, - customActions: widget.customActions, - sortOptions: widget.sortOptions, - isSelecting: _isSelecting, - handleCustomAction: _handleCustomAction, - handleEndSelection: _endSelection, - handleDeleteAction: _handleDeleteAction, - handleSelectAll: _handleSelectAll, - selectedIds: _selectedIds, - handleFilterChange: _handleFilterChange, - selectedSortIndex: _selectedSortIndex, - handleSortChange: _handleSortChange), + listFilters: widget.listFilters, + customActions: widget.customActions, + sortOptions: widget.sortOptions, + isSelecting: _isSelecting, + handleCustomAction: _handleCustomAction, + handleEndSelection: _endSelection, + handleDeleteAction: _handleDeleteAction, + handleSelectAll: _handleSelectAll, + selectedIds: _selectedIds, + handleFilterChange: _handleFilterChange, + selectedSortIndex: _selectedSortIndex, + handleSortChange: _handleSortChange, + isDeleteEnabled: widget.isDeleteEnabled, + ), if (widget.header != null) widget.header!, Expanded( flex: 1, diff --git a/lib/common/widgets/list/list_filter_bar.dart b/lib/common/widgets/list/list_filter_bar.dart index a6d87910..9f1a9ded 100644 --- a/lib/common/widgets/list/list_filter_bar.dart +++ b/lib/common/widgets/list/list_filter_bar.dart @@ -21,12 +21,14 @@ class ListFilterBar extends StatelessWidget { required this.selectedIds, required this.handleFilterChange, required this.selectedSortIndex, - required this.handleSortChange}); + required this.handleSortChange, + required this.isDeleteEnabled}); final List> listFilters; final List> customActions; final List> sortOptions; final bool isSelecting; + final bool isDeleteEnabled; final Function(ListFilterCustomAction) handleCustomAction; final Function handleEndSelection; final void Function() handleFilterChange; @@ -67,12 +69,13 @@ class ListFilterBar extends StatelessWidget { action: () => handleCustomAction(action), ), ), - ListFilterAction( - name: AppLocalizations.of(context)!.deleteAllFilteredAction, - icon: Icons.delete_rounded, - color: colorScheme.error, - action: handleDeleteAction, - ) + if (isDeleteEnabled) + ListFilterAction( + name: AppLocalizations.of(context)!.deleteAllFilteredAction, + icon: Icons.delete_rounded, + color: colorScheme.error, + action: handleDeleteAction, + ) ], activeFilterCount: activeFilterCount + (isSelecting ? 1 : 0), ), diff --git a/lib/debug/data/log_list_filters.dart b/lib/debug/data/log_list_filters.dart new file mode 100644 index 00000000..4af09485 --- /dev/null +++ b/lib/debug/data/log_list_filters.dart @@ -0,0 +1,24 @@ +import 'package:clock_app/alarm/types/alarm_event.dart'; +import 'package:clock_app/common/types/list_filter.dart'; +import 'package:clock_app/common/utils/date_time.dart'; +import 'package:clock_app/debug/types/log.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:logger/logger.dart'; + +final List> logListFilters = [ + ListFilterSelect((context) => AppLocalizations.of(context)!.dateFilterGroup, [ + ListFilter((context) => AppLocalizations.of(context)!.todayFilter, + (log) => log.dateTime.isToday()), + ListFilter((context) => AppLocalizations.of(context)!.tomorrowFilter, + (log) => log.dateTime.isTomorrow()), + ]), + ListFilterMultiSelect( + (context) => AppLocalizations.of(context)!.logTypeFilterGroup, [ + ListFilter((context) => "Debug", (log) => log.level == Level.debug), + ListFilter((context) => "Trace", (log) => log.level == Level.trace), + ListFilter((context) => "Info", (log) => log.level == Level.info), + ListFilter((context) => "Warning", (log) => log.level == Level.warning), + ListFilter((context) => "Error", (log) => log.level == Level.error), + ListFilter((context) => "Fatal", (log) => log.level == Level.fatal), + ]), +]; diff --git a/lib/debug/data/log_sort_options.dart b/lib/debug/data/log_sort_options.dart new file mode 100644 index 00000000..d5a860ec --- /dev/null +++ b/lib/debug/data/log_sort_options.dart @@ -0,0 +1,17 @@ +import 'package:clock_app/common/types/list_filter.dart'; +import 'package:clock_app/debug/types/log.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; + +final List> logSortOptions = [ + ListSortOption((context) => "Earlies first", sortDateAscending), + ListSortOption((context) => "Latest first", sortDateDescending), +]; + +int sortDateAscending(Log a, Log b) { + return a.id.compareTo(b.id); +} + +int sortDateDescending(Log a, Log b) { + return b.id.compareTo(a.id); +} + diff --git a/lib/debug/logic/logger.dart b/lib/debug/logic/logger.dart index 6c80b915..8d66a7a9 100644 --- a/lib/debug/logic/logger.dart +++ b/lib/debug/logic/logger.dart @@ -7,7 +7,8 @@ var logger = Logger( filter: FileLogFilter(), output: FileLoggerOutput(), printer: PrettyPrinter( - methodCount: 0, // Number of method calls to be displayed + methodCount: 100, // Number of method calls to be displayed + errorMethodCount: 8, // Number of method calls if stacktrace is provided lineLength: 80, // Width of the output colors: true, // Colorful log messages diff --git a/lib/debug/screens/logs_screen.dart b/lib/debug/screens/logs_screen.dart new file mode 100644 index 00000000..96bee61d --- /dev/null +++ b/lib/debug/screens/logs_screen.dart @@ -0,0 +1,194 @@ +import 'dart:io'; + +import 'package:clock_app/alarm/data/alarm_events_list_filters.dart'; +import 'package:clock_app/common/data/paths.dart'; +import 'package:clock_app/common/types/list_controller.dart'; +import 'package:clock_app/common/utils/snackbar.dart'; +import 'package:clock_app/common/widgets/fab.dart'; +import 'package:clock_app/common/widgets/list/custom_list_view.dart'; +import 'package:clock_app/debug/data/log_list_filters.dart'; +import 'package:clock_app/debug/data/log_sort_options.dart'; +import 'package:clock_app/debug/types/log.dart'; +import 'package:clock_app/debug/widgets/log_card.dart'; +import 'package:clock_app/navigation/widgets/app_top_bar.dart'; +import 'package:clock_app/settings/types/setting_item.dart'; +import 'package:flutter/material.dart'; +import 'package:pick_or_save/pick_or_save.dart'; + +class LogsScreen extends StatefulWidget { + const LogsScreen({ + super.key, + }); + + @override + State createState() => _LogsScreenState(); +} + +class _LogsScreenState extends State { + List _logs = []; + final _listController = ListController(); + + List _mergeMultilineLogs(List logLines) { + final mergedLogs = []; + final buffer = StringBuffer(); + + for (var line in logLines) { + if (line.startsWith('[')) { + if (buffer.isNotEmpty) { + mergedLogs.add(buffer.toString()); + buffer.clear(); + } + buffer.writeln(line.trim()); + } else { + buffer.writeln(line.trim()); + } + } + + if (buffer.isNotEmpty) { + mergedLogs.add(buffer.toString()); + } + + // remove new lines from the end of the logs + for (var i = 0; i < mergedLogs.length; i++) { + mergedLogs[i] = mergedLogs[i].trim(); + } + + return mergedLogs; + } + + @override + void initState() { + final File file = File(getLogsFilePathSync()); + final content = file.readAsLinesSync(); + final logLines = _mergeMultilineLogs(content); + + for (int i = 0; i < logLines.length; i++) { + _logs.add(Log.fromLine(logLines[i], i)); + } + + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + ThemeData theme = Theme.of(context); + TextTheme textTheme = theme.textTheme; + + return Scaffold( + appBar: AppTopBar(title: Text("App Logs", style: textTheme.titleMedium)), + body: Stack( + children: [ + Column( + children: [ + Expanded( + flex: 1, + child: CustomListView( + items: _logs, + listController: _listController, + itemBuilder: (log) => LogCard( + key: ValueKey(log), + log: log, + ), + // onTapItem: (fileItem, index) { + // // widget.setting.setValue(context, themeItem); + // // _listController.reload(); + // }, + // onDeleteItem: (event){}, + isDuplicateEnabled: false, + isReorderable: false, + isDeleteEnabled: false, + // isDeleteEnabled: true, + placeholderText: "No logs", + listFilters: logListFilters, + sortOptions: logSortOptions, + // reloadOnPop: true, + // listFilters: alarmEventsListFilters, + ), + ), + ], + ), + FAB( + icon: Icons.delete_rounded, + bottomPadding: 8, + onPressed: () async { + final File file = File(await getLogsFilePath()); + + await file.writeAsString(""); + + if (context.mounted) showSnackBar(context, "Logs cleared"); + + _listController.clearItems(); + }, + ), + FAB( + index: 1, + icon: Icons.file_download, + bottomPadding: 8, + onPressed: () async { + final File file = File(await getLogsFilePath()); + + if (!(await file.exists())) { + await file.create(recursive: true); + } + + final result = await PickOrSave().fileSaver( + params: FileSaverParams( + saveFiles: [ + SaveFileInfo( + fileData: await file.readAsBytes(), + fileName: + "chrono_logs_${DateTime.now().toIso8601String().split(".")[0]}.txt", + ) + ], + )); + if (result != null) { + if (context.mounted) { + showSnackBar(context, "Logs saved to device"); + } + } + }), + // FAB( + // index: 2, + // icon: Icons.file_upload, + // bottomPadding: 8, + // onPressed: () async { + // List? result = await PickOrSave().filePicker( + // params: FilePickerParams( + // getCachedFilePath: true, + // ), + // ); + // if (result != null && result.isNotEmpty) { + // File file = File(result[0]); + // final data = utf8.decode(file.readAsBytesSync()); + // final alarmEvents = listFromString(data); + // for (var event in alarmEvents) { + // _listController.addItem(event); + // } + // } + // }), + + // FAB( + // index: 1, + // icon: Icons.folder_rounded, + // bottomPadding: 8, + // onPressed: () async { + // // Item? themeItem = widget.createThemeItem(); + // // await _openCustomizeItemScreen( + // // themeItem, + // // onSave: (newThemeItem) { + // // _listController.addItem(newThemeItem); + // // }, + // // isNewItem: true, + // // ); + // }, + // ) + ], + ), + ); + } +} diff --git a/lib/debug/types/log.dart b/lib/debug/types/log.dart new file mode 100644 index 00000000..468aae1a --- /dev/null +++ b/lib/debug/types/log.dart @@ -0,0 +1,68 @@ +import 'package:clock_app/common/types/json.dart'; +import 'package:clock_app/common/types/list_item.dart'; +import 'package:clock_app/common/utils/id.dart'; +import 'package:intl/intl.dart'; +import 'package:logger/logger.dart'; + +class Log extends ListItem { + @override + final int id; + late String message; + late DateTime dateTime; + late Level level; + + Log( + this.id, + this.message, + this.dateTime, + this.level, + ); + + Log.fromLine(String line, int index) : id = index { + final regex = RegExp( + r'\[(\d+-\d+-\d+)\s\|\s(\d+:\d+:\d+)\]\s\[(\w+)\]\s(.*)', + dotAll: true); + final match = regex.firstMatch(line); + + if (match != null) { + final datePart = match.group(1)!; + final timePart = match.group(2)!; + level = Level.values.byName(match.group(3)!); + message = match.group(4)!; + + final dateTimeStr = '$datePart $timePart'; + final dateFormat = DateFormat('d-M-yyyy HH:mm:ss'); + dateTime = dateFormat.parse(dateTimeStr); + } else { + message = "Cannot read log"; + level = Level.off; + dateTime = DateTime.now(); + throw const FormatException('Invalid log format'); + } + } + + @override + copy() { + return Log(id,message, dateTime, level); + } + + @override + void copyFrom(other) { + message = other.message; + dateTime = other.timestamp; + level = other.level; + } + + @override + bool get isDeletable => false; + + @override + Json? toJson() { + return { + "id": id, + "message": message, + "timestamp": dateTime.toIso8601String(), + "level": level.toString(), + }; + } +} diff --git a/lib/debug/widgets/log_card.dart b/lib/debug/widgets/log_card.dart new file mode 100644 index 00000000..7977bcd0 --- /dev/null +++ b/lib/debug/widgets/log_card.dart @@ -0,0 +1,78 @@ +import 'package:clock_app/debug/types/log.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:logger/logger.dart'; + +class LogCard extends StatefulWidget { + const LogCard({ + super.key, + required this.log, + }); + + final Log log; + + @override + State createState() => _LogCardState(); +} + +class LevelColor { + Color backgroundColor; + Color textColor; + + LevelColor(this.backgroundColor, this.textColor); +} + +Map levelColors = { + Level.debug: LevelColor(Colors.brown, Colors.white), + Level.trace: LevelColor(Colors.grey, Colors.white), + Level.info: LevelColor(Colors.blue, Colors.white), + Level.warning: LevelColor(Colors.orange, Colors.white), + Level.error: LevelColor(Colors.red, Colors.white), + Level.fatal: LevelColor(Colors.purple, Colors.white), +}; + +class _LogCardState extends State { + @override + Widget build(BuildContext context) { + return Padding( + padding: + const EdgeInsets.only(left: 16.0, right: 16.0, top: 8, bottom: 8), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + padding: + const EdgeInsets.symmetric(horizontal: 4, vertical: 2), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(4), + color: levelColors[widget.log.level]?.backgroundColor, + ), + child: Text(widget.log.level.name, + style: Theme.of(context).textTheme.bodySmall?.copyWith( + color: levelColors[widget.log.level]?.textColor, + )), + ), + const SizedBox( + width: 8, + ), + Text( + DateFormat('yyyy-MM-dd | kk:mm:ss') + .format(widget.log.dateTime), + style: Theme.of(context).textTheme.bodySmall, + ), + const Spacer(), + ], + ), + Text( + widget.log.message, + style: Theme.of(context).textTheme.bodyMedium, + // maxLines: 1, + // overflow: TextOverflow.ellipsis, + // softWrap: false, + ), + ], + )); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4214c373..46c37312 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -169,6 +169,8 @@ "@maxLogsSetting": {}, "alarmLogSetting": "Alarm logs", "@alarmLogSetting": {}, + "appLogs": "App logs", + "@appLogs": {}, "saveLogs": "Save logs", "@saveLogs": {}, "showErrorSnackbars": "Show error snackbars", diff --git a/lib/main.dart b/lib/main.dart index 2b2337a9..358d5cce 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -27,9 +27,9 @@ import 'package:flutter_show_when_locked/flutter_show_when_locked.dart'; import 'package:timezone/data/latest_all.dart'; void main() async { - FlutterError.onError = (FlutterErrorDetails details) { - logger.f(details.exception.toString()); - }; + // FlutterError.onError = (FlutterErrorDetails details) { + // logger.f(details.exception.toString()); + // }; WidgetsFlutterBinding.ensureInitialized(); diff --git a/lib/settings/data/developer_settings_schema.dart b/lib/settings/data/developer_settings_schema.dart index 28285fd3..81a16e2f 100644 --- a/lib/settings/data/developer_settings_schema.dart +++ b/lib/settings/data/developer_settings_schema.dart @@ -1,16 +1,13 @@ import 'dart:io'; import 'package:clock_app/alarm/screens/alarm_events_screen.dart'; -import 'package:clock_app/common/data/paths.dart'; -import 'package:clock_app/common/utils/snackbar.dart'; +import 'package:clock_app/debug/screens/logs_screen.dart'; import 'package:clock_app/settings/types/setting.dart'; -import 'package:clock_app/settings/types/setting_action.dart'; import 'package:clock_app/settings/types/setting_group.dart'; import 'package:clock_app/settings/types/setting_link.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -import 'package:pick_or_save/pick_or_save.dart'; SettingGroup developerSettingsSchema = SettingGroup( "Developer Options", @@ -40,36 +37,12 @@ SettingGroup developerSettingsSchema = SettingGroup( "alarm_logs", (context) => AppLocalizations.of(context)!.alarmLogSetting, const AlarmEventsScreen()), - SettingAction( - "save_logs", (context) => AppLocalizations.of(context)!.saveLogs, - (context) async { - final File file = File(await getLogsFilePath()); + SettingPageLink( + "app_logs", + (context) => AppLocalizations.of(context)!.appLogs, + const LogsScreen()), - if(!(await file.exists())) { - await file.create(recursive: true); - } - - await PickOrSave().fileSaver( - params: FileSaverParams( - saveFiles: [ - SaveFileInfo( - fileData: await file.readAsBytes(), - fileName: - "chrono_logs_${DateTime.now().toIso8601String().split(".")[0]}.txt", - ) - ], - )); - }), - SettingAction( - "clear_logs", (context) => AppLocalizations.of(context)!.clearLogs, - (context) async { - final File file = File(await getLogsFilePath()); - - await file.writeAsString(""); - - if(context.mounted) showSnackBar(context, "Logs cleared"); - }) - ]), + ]), ], icon: Icons.code_rounded, ); diff --git a/lib/settings/screens/logs_screen.dart b/lib/settings/screens/logs_screen.dart deleted file mode 100644 index e69de29b..00000000 diff --git a/lib/system/logic/background_service.dart b/lib/system/logic/background_service.dart index 2e543172..cf59083c 100644 --- a/lib/system/logic/background_service.dart +++ b/lib/system/logic/background_service.dart @@ -1,7 +1,9 @@ import 'package:background_fetch/background_fetch.dart'; import 'package:clock_app/alarm/logic/update_alarms.dart'; import 'package:clock_app/debug/logic/logger.dart'; +import 'package:clock_app/system/logic/initialize_isolate.dart'; import 'package:clock_app/timer/logic/update_timers.dart'; +import 'package:flutter/material.dart'; Future initBackgroundService() async { await BackgroundFetch.configure( @@ -20,8 +22,10 @@ Future initBackgroundService() async { // await initializeIsolate(); - await updateAlarms("initBackgroundService(): Update alarms in background service"); - await updateTimers("initBackgroundService(): Update timers in background service"); + await updateAlarms( + "initBackgroundService(): Update alarms in background service"); + await updateTimers( + "initBackgroundService(): Update timers in background service"); // IMPORTANT: You must signal completion of your task or the OS can punish your app // for taking too long in the background. BackgroundFetch.finish(taskId); @@ -36,18 +40,24 @@ Future initBackgroundService() async { // [Android-only] This "Headless Task" is run when the Android app is terminated with `enableHeadless: true` @pragma('vm:entry-point') void handleBackgroundServiceTask(HeadlessTask task) async { + FlutterError.onError = (FlutterErrorDetails details) { + logger.f("Error in handleBackgroundServiceTask isolate: ${details.exception.toString()}"); + }; String taskId = task.taskId; bool isTimeout = task.timeout; if (isTimeout) { - // This task has exceeded its allowed running-time. + // This task has exceeded its allowed running-time. // You must stop what you're doing and immediately .finish(taskId) logger.i("[BackgroundFetch] Headless task timed-out: $taskId"); BackgroundFetch.finish(taskId); return; - } + } + await initializeIsolate(); logger.i('[BackgroundFetch] Headless event received.'); - await updateAlarms("handleBackgroundServiceTask(): Update alarms in background service"); - await updateTimers("handleBackgroundServiceTask(): Update timers in background service"); + await updateAlarms( + "handleBackgroundServiceTask(): Update alarms in background service"); + await updateTimers( + "handleBackgroundServiceTask(): Update timers in background service"); BackgroundFetch.finish(taskId); } diff --git a/lib/system/logic/handle_boot.dart b/lib/system/logic/handle_boot.dart index 742c589b..a994db9a 100644 --- a/lib/system/logic/handle_boot.dart +++ b/lib/system/logic/handle_boot.dart @@ -1,6 +1,8 @@ import 'package:clock_app/alarm/logic/update_alarms.dart'; +import 'package:clock_app/debug/logic/logger.dart'; import 'package:clock_app/system/logic/initialize_isolate.dart'; import 'package:clock_app/timer/logic/update_timers.dart'; +import 'package:flutter/material.dart'; @pragma('vm:entry-point') void handleBoot() async { @@ -11,6 +13,10 @@ void handleBoot() async { // File('$appDataDirectory/log-dart.txt') // .writeAsStringSync(message, mode: FileMode.append); // + FlutterError.onError = (FlutterErrorDetails details) { + logger.f("Error in handleBoot isolate: ${details.exception.toString()}"); + }; + await initializeIsolate(); await updateAlarms("handleBoot(): Update alarms on system boot");