diff --git a/lib/app/modules/logs/bindings/logs_binding.dart b/lib/app/modules/logs/bindings/logs_binding.dart new file mode 100644 index 00000000..46a37ce8 --- /dev/null +++ b/lib/app/modules/logs/bindings/logs_binding.dart @@ -0,0 +1,12 @@ +import 'package:get/get.dart'; + +import '../controllers/logs_controller.dart'; + +class LogsBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => LogsController(), + ); + } +} diff --git a/lib/app/modules/logs/controllers/logs_controller.dart b/lib/app/modules/logs/controllers/logs_controller.dart new file mode 100644 index 00000000..c4873eb2 --- /dev/null +++ b/lib/app/modules/logs/controllers/logs_controller.dart @@ -0,0 +1,26 @@ +import 'package:get/get.dart'; +import 'package:taskwarrior/app/utils/debug_logger/log_databse_helper.dart'; + +class LogsController extends GetxController { + final LogDatabaseHelper _logDatabaseHelper = LogDatabaseHelper(); + var logs = >[].obs; + var isLoading = true.obs; + + @override + void onInit() { + super.onInit(); + loadLogs(); + } + + Future loadLogs() async { + isLoading.value = true; + final fetchedLogs = await _logDatabaseHelper.getLogs(); + logs.assignAll(fetchedLogs); // Update the RxList + isLoading.value = false; + } + + Future clearLogs() async { + await _logDatabaseHelper.clearLogs(); + loadLogs(); + } +} diff --git a/lib/app/modules/logs/views/logs_view.dart b/lib/app/modules/logs/views/logs_view.dart new file mode 100644 index 00000000..b0096702 --- /dev/null +++ b/lib/app/modules/logs/views/logs_view.dart @@ -0,0 +1,78 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:taskwarrior/app/utils/debug_logger/log_databse_helper.dart'; +import '../controllers/logs_controller.dart'; + +class LogsView extends GetView { + const LogsView({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Debug Logs'), + actions: [ + Obx( + () => IconButton( + icon: const Icon(Icons.refresh), + onPressed: + controller.isLoading.value ? null : controller.loadLogs, + tooltip: 'Refresh Logs', + ), + ), + Obx( + () => IconButton( + icon: const Icon(Icons.delete_forever), + onPressed: + controller.isLoading.value ? null : controller.clearLogs, + tooltip: 'Clear All Logs', + ), + ), + ], + ), + body: Obx( + () { + if (controller.isLoading.value) { + return const Center(child: CircularProgressIndicator()); + } else if (controller.logs.isEmpty) { + return const Center( + child: Text( + 'No debug logs found.', + style: TextStyle(fontSize: 16, color: Colors.grey), + ), + ); + } else { + return ListView.builder( + padding: const EdgeInsets.all(8.0), + itemCount: controller.logs.length, + itemBuilder: (context, index) { + final log = controller.logs[index]; + return Card( + margin: const EdgeInsets.symmetric(vertical: 4.0), + child: Padding( + padding: const EdgeInsets.all(12.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + log[LogDatabaseHelper.columnMessage] ?? 'No message', + style: const TextStyle(fontSize: 15), + ), + const SizedBox(height: 4), + Text( + 'Timestamp: ${log[LogDatabaseHelper.columnTimestamp] ?? 'N/A'}', + style: + const TextStyle(fontSize: 12, color: Colors.grey), + ), + ], + ), + ), + ); + }, + ); + } + }, + ), + ); + } +} diff --git a/lib/app/modules/settings/views/settings_page_body.dart b/lib/app/modules/settings/views/settings_page_body.dart index 0fb45153..e7ef440c 100644 --- a/lib/app/modules/settings/views/settings_page_body.dart +++ b/lib/app/modules/settings/views/settings_page_body.dart @@ -5,6 +5,7 @@ import 'package:get/get.dart'; import 'package:google_fonts/google_fonts.dart'; import 'package:taskwarrior/app/modules/settings/views/settings_page_taskchampion.dart'; +import 'package:taskwarrior/app/routes/app_pages.dart'; import 'package:taskwarrior/app/utils/constants/taskwarrior_fonts.dart'; import 'package:taskwarrior/app/utils/language/sentence_manager.dart'; import 'package:taskwarrior/app/utils/themes/theme_extension.dart'; @@ -24,7 +25,8 @@ class SettingsPageBody extends StatelessWidget { @override Widget build(BuildContext context) { - TaskwarriorColorTheme tColors = Theme.of(context).extension()!; + TaskwarriorColorTheme tColors = + Theme.of(context).extension()!; return Obx(() { if (controller.isMovingDirectory.value) { return Center( @@ -138,6 +140,16 @@ class SettingsPageBody extends StatelessWidget { controller: controller, ), ), + const Divider(), + SettingsPageListTile( + title: "Logs ", + subTitle: "check all debug logs here", + trailing: IconButton( + onPressed: () { + Get.toNamed(Routes.LOGS); + }, + icon: const Icon(Icons.login)), + ) ], ); } diff --git a/lib/app/routes/app_pages.dart b/lib/app/routes/app_pages.dart index 276a297d..c866376b 100644 --- a/lib/app/routes/app_pages.dart +++ b/lib/app/routes/app_pages.dart @@ -6,6 +6,8 @@ import '../modules/detailRoute/bindings/detail_route_binding.dart'; import '../modules/detailRoute/views/detail_route_view.dart'; import '../modules/home/bindings/home_binding.dart'; import '../modules/home/views/home_view.dart'; +import '../modules/logs/bindings/logs_binding.dart'; +import '../modules/logs/views/logs_view.dart'; import '../modules/manageTaskServer/bindings/manage_task_server_binding.dart'; import '../modules/manageTaskServer/views/manage_task_server_view.dart'; import '../modules/manage_task_champion_creds/bindings/manage_task_champion_creds_binding.dart'; @@ -88,5 +90,10 @@ class AppPages { page: () => const ManageTaskChampionCredsView(), binding: ManageTaskChampionCredsBinding(), ), + GetPage( + name: _Paths.LOGS, + page: () => const LogsView(), + binding: LogsBinding(), + ), ]; } diff --git a/lib/app/routes/app_routes.dart b/lib/app/routes/app_routes.dart index 0729f447..b93194a2 100644 --- a/lib/app/routes/app_routes.dart +++ b/lib/app/routes/app_routes.dart @@ -16,6 +16,7 @@ abstract class Routes { static const SETTINGS = _Paths.SETTINGS; static const PERMISSION = _Paths.PERMISSION; static const MANAGE_TASK_CHAMPION_CREDS = _Paths.MANAGE_TASK_CHAMPION_CREDS; + static const LOGS = _Paths.LOGS; } abstract class _Paths { @@ -31,4 +32,5 @@ abstract class _Paths { static const SETTINGS = '/settings'; static const PERMISSION = '/permission'; static const MANAGE_TASK_CHAMPION_CREDS = '/manage-task-champion-creds'; + static const LOGS = '/logs'; } diff --git a/lib/app/utils/debug_logger/log_databse_helper.dart b/lib/app/utils/debug_logger/log_databse_helper.dart new file mode 100644 index 00000000..f897ef7d --- /dev/null +++ b/lib/app/utils/debug_logger/log_databse_helper.dart @@ -0,0 +1,58 @@ +import 'package:sqflite/sqflite.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'dart:io'; + +class LogDatabaseHelper { + static Database? _database; + static const String tableName = 'logs'; + static const String columnId = 'id'; + static const String columnMessage = 'message'; + static const String columnTimestamp = 'timestamp'; + + Future get database async { + if (_database != null) return _database!; + _database = await initDB(); + return _database!; + } + + Future initDB() async { + Directory documentsDirectory = await getApplicationDocumentsDirectory(); + String path = join(documentsDirectory.path, 'debug_logs.db'); + return await openDatabase( + path, + version: 1, + onCreate: (db, version) async { + await db.execute(''' + CREATE TABLE $tableName ( + $columnId INTEGER PRIMARY KEY AUTOINCREMENT, + $columnMessage TEXT NOT NULL, + $columnTimestamp TEXT NOT NULL + ) + '''); + }, + ); + } + + Future insertLog(String message) async { + final db = await database; + await db.insert( + tableName, + { + columnMessage: message, + columnTimestamp: DateTime.now().toIso8601String(), + }, + conflictAlgorithm: ConflictAlgorithm.replace, + ); + } + + Future>> getLogs() async { + final db = await database; + return await db.query(tableName, orderBy: '$columnTimestamp DESC'); + } + + Future clearLogs() async { + final db = await database; + await db.delete(tableName); + } +} diff --git a/lib/main.dart b/lib/main.dart index 3fc97d3b..8fb861a2 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,11 +1,22 @@ +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:taskwarrior/app/utils/app_settings/app_settings.dart'; +import 'package:taskwarrior/app/utils/debug_logger/log_databse_helper.dart'; import 'package:taskwarrior/app/utils/themes/dark_theme.dart'; import 'package:taskwarrior/app/utils/themes/light_theme.dart'; import 'app/routes/app_pages.dart'; +LogDatabaseHelper _logDatabaseHelper = LogDatabaseHelper(); + void main() async { + debugPrint = (String? message, {int? wrapWidth}) { + if (message != null) { + debugPrintSynchronously(message, wrapWidth: wrapWidth); + _logDatabaseHelper.insertLog(message); + } + }; + WidgetsFlutterBinding.ensureInitialized(); await AppSettings.init();